PHP torna relativamente fácil construir um sistema baseado na web, o que é um dos motivos de sua popularidade. Mas apesar de sua facilidade de uso, PHP evoluiu para uma linguagem bastante sofisticada com muitas estruturas, nuances e sutilezas que podem incomodar os desenvolvedores, levando a horas de depuração complicada. Este artigo destaca dez dos erros mais comuns que Desenvolvedores PHP precisa ter cuidado.
foreach
rotaçõesNão sabe como usar loops foreach em PHP? Usando referências em foreach
os loops podem ser úteis se você quiser operar em cada elemento do array sobre o qual está iterando. Por exemplo:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
O problema é que, se você não tomar cuidado, isso também pode ter alguns efeitos colaterais e consequências indesejáveis. Especificamente, no exemplo acima, depois que o código é executado, $value
permanecerá no escopo e conterá uma referência ao último elemento na matriz. Operações subsequentes envolvendo $value
poderia, portanto, involuntariamente acabar modificando o último elemento na matriz.
A principal coisa a lembrar é que foreach
não cria um escopo. Portanto, $value
no exemplo acima é um referência dentro do escopo superior do script. Em cada iteração foreach
define a referência para apontar para o próximo elemento de $array
. Após a conclusão do loop, portanto, $value
ainda aponta para o último elemento de $array
e permanece no escopo.
Aqui está um exemplo do tipo de bug evasivo e confuso que isso pode causar:
$array = [1, 2, 3]; echo implode(',', $array), '
'; foreach ($array as &$value) {} // by reference echo implode(',', $array), '
'; foreach ($array as $value) {} // by value (i.e., copy) echo implode(',', $array), '
';
O código acima irá gerar o seguinte:
1,2,3 1,2,3 1,2,2
Não, isso não é um erro de digitação. O último valor na última linha é de fato 2, não 3.
Por quê?
Depois de passar pelo primeiro foreach
loop, $array
permanece inalterado, mas, conforme explicado acima, $value
é deixado como uma referência pendente para o último elemento em $array
(uma vez que foreach
loop acessado $value
por referência )
Como resultado, quando passamos pelo segundo foreach
loop, “coisas estranhas” parecem acontecer. Especificamente, uma vez que $value
agora está sendo acessado por valor (ou seja, por cópia de ), foreach
cópias cada sequencial $array
elemento em $value
em cada etapa do loop. Como resultado, aqui está o que acontece durante cada etapa do segundo foreach
ciclo:
$array[0]
(ou seja, “1”) em $value
(que é uma referência a $array[2]
), então $array[2]
agora é igual a 1. Portanto, $array
agora contém [1, 2, 1].$array[1]
(ou seja, “2”) em $value
(que é uma referência a $array[2]
), então $array[2]
agora é igual a 2. Portanto $array
agora contém [1, 2, 2].$array[2]
(que agora é igual a “2”) em $value
(que é uma referência a $array[2]
), então $array[2]
ainda é igual a 2. Portanto, $array
agora contém [1, 2, 2].Para ainda obter o benefício de usar referências em foreach
loops sem correr o risco desses tipos de problemas, chame unset()
na variável, imediatamente após foreach
loop, para remover a referência; por exemplo.:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
isset()
comportamentoApesar do nome, isset()
não só retorna falso se um item não existe, mas também retorna false
para null
valores .
Esse comportamento é mais problemático do que pode parecer à primeira vista e é uma fonte comum de problemas.
Considere o seguinte:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
O autor deste código provavelmente queria verificar se keyShouldBeSet
foi definido em $data
. Mas, conforme discutido, isset($data['keyShouldBeSet'])
vai Além disso retorna falso se $data['keyShouldBeSet']
foi definido, mas foi definido como null
. Portanto, a lógica acima é falha.
Aqui está outro exemplo:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
O código acima assume que if $_POST['active']
retorna true
, então postData
será necessariamente definido e, portanto, isset($postData)
retornará true
. Por outro lado, o código acima assume que o só maneira que isset($postData)
retornará false
é se $_POST['active']
devolvido false
também.
Não.
Conforme explicado, isset($postData)
também retornará false
if $postData
foi definido como null
. Portanto é possível para isset($postData)
para retornar false
mesmo se $_POST['active']
retornado true
. Então, novamente, a lógica acima é falha.
E, a propósito, como um ponto lateral, se a intenção no código acima realmente era verificar novamente se $_POST['active']
retornou verdadeiro, contando com isset()
pois esta foi uma má decisão de codificação em qualquer caso. Em vez disso, teria sido melhor apenas verificar novamente $_POST['active']
; ou seja:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
Para casos, porém, onde é importante verificar se uma variável foi realmente definida (ou seja, para distinguir entre uma variável que não foi definida e uma variável que foi definida como null
), o array_key_exists()
método é uma solução muito mais robusta.
Por exemplo, poderíamos reescrever o primeiro dos dois exemplos acima da seguinte maneira:
melhor lugar para aprender c
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
Além disso, combinando array_key_exists()
com get_defined_vars()
, podemos verificar com segurança se uma variável dentro do escopo atual foi definida ou não:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Considere este snippet de código:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Se você executar o código acima, obterá o seguinte:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
O que está errado?
O problema é que o código acima confunde o retorno de arrays por referência com o retorno de arrays por valor. A menos que você diga explicitamente ao PHP para retornar um array por referência (ou seja, usando &
), o PHP irá, por padrão, retornar o array “por valor”. Isso significa que um cópia de do array será retornado e, portanto, a função chamada e o chamador não estarão acessando a mesma instância do array.
Portanto, a chamada acima para getValues()
retorna um cópia de do $values
array em vez de uma referência a ele. Com isso em mente, vamos revisitar as duas linhas principais do exemplo acima:
// getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the 'undefined index' message). echo $config->getValues()['test'];
Uma possível correção seria salvar a primeira cópia do arquivo $values
array retornado por getValues()
e operar nessa cópia posteriormente; por exemplo.:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
Esse código funcionará bem (ou seja, ele produzirá test
sem gerar nenhuma mensagem de 'índice indefinido'), mas dependendo do que você está tentando realizar, essa abordagem pode ou não ser adequada. Em particular, o código acima não modificará o original $values
array. Então, se você Faz deseja que suas modificações (como adicionar um elemento de 'teste') afetem a matriz original, em vez disso, você precisará modificar o getValues()
função para retornar um referência para o $values
próprio array. Isso é feito adicionando um &
antes do nome da função, indicando que deve retornar uma referência; ou seja:
class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
A saída disso será test
, conforme esperado.
Mas para tornar as coisas mais confusas, considere, em vez disso, o seguinte snippet de código:
class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Se você adivinhou que isso resultaria no mesmo erro de “índice indefinido” que nosso anterior array
exemplo, você estava errado. De fato, isto o código funcionará bem. A razão é que, ao contrário dos arrays, PHP sempre passa objetos por referência . (ArrayObject
é um objeto SPL, que imita totalmente o uso de matrizes, mas funciona como um objeto.)
Como esses exemplos demonstram, nem sempre é totalmente óbvio no PHP se você está lidando com uma cópia ou uma referência. Portanto, é essencial entender esses comportamentos padrão (ou seja, variáveis e arrays são passados por valor; objetos são passados por referência) e também verificar cuidadosamente a documentação da API para a função que você está chamando para ver se ela está retornando um valor, um cópia de uma matriz, uma referência a uma matriz ou uma referência a um objeto.
Dito isso, é importante observar que a prática de retornar uma referência a uma matriz ou a um ArrayObject
geralmente é algo que deve ser evitado, pois fornece ao chamador a capacidade de modificar os dados privados da instância. Isso “vai na cara” do encapsulamento. Em vez disso, é melhor usar o estilo antigo 'getters' e 'setters', por exemplo:
class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'
Essa abordagem dá ao chamador a capacidade de definir ou obter qualquer valor na matriz sem fornecer acesso público ao $values
próprio array.
Não é incomum encontrar algo assim se o seu PHP não estiver funcionando:
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Embora possa não haver absolutamente nada de errado aqui, mas se você seguir a lógica do código, poderá descobrir que a aparência inocente chama acima para $valueRepository->findByValue()
no final das contas resulta em algum tipo de consulta, como:
$result = $connection->query('SELECT `x`,`y` FROM `values` WHERE `value`=' . $inputValue);
Como resultado, cada iteração do loop acima resultaria em uma consulta separada ao banco de dados. Portanto, se, por exemplo, você forneceu uma matriz de 1.000 valores para o loop, ele geraria 1.000 consultas separadas para o recurso! Se tal script for chamado em vários threads, ele pode potencialmente levar o sistema a uma paralisação.
Portanto, é crucial reconhecer quando as consultas estão sendo feitas por seu código e, sempre que possível, reunir os valores e, em seguida, executar uma consulta para buscar todos os resultados.
Um exemplo de um lugar bastante comum para encontrar consultas sendo feitas de forma ineficiente (ou seja, em um loop) é quando um formulário é postado com uma lista de valores (IDs, por exemplo). Em seguida, para recuperar os dados de registro completos de cada um dos IDs, o código percorrerá a matriz e fará uma consulta SQL separada para cada ID. Isso geralmente será algo assim:
$data = []; foreach ($ids as $id) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` = ' . $id); $data[] = $result->fetch_row(); }
Mas a mesma coisa pode ser realizada com muito mais eficiência em um solteiro Consulta SQL da seguinte forma:
$data = []; if (count($ids)) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` IN (' . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Portanto, é crucial reconhecer quando as consultas estão sendo feitas, direta ou indiretamente, por seu código. Sempre que possível, reúna os valores e execute uma consulta para buscar todos os resultados. Ainda assim, deve-se ter cuidado lá também, o que nos leva ao nosso próximo erro comum de PHP ...
Embora buscar muitos registros de uma vez seja definitivamente mais eficiente do que executar uma única consulta para cada linha a ser buscada, tal abordagem pode potencialmente levar a uma condição de “falta de memória” em libmysqlclient
ao usar PHP’s mysql
extensão.
Para demonstrar, vamos dar uma olhada em uma caixa de teste com recursos limitados (512 MB de RAM), MySQL e php-cli
.
Vamos inicializar uma tabela de banco de dados como esta:
// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col query($query); // write 2 million rows for ($row = 0; $row <2000000; $row++) { $query = 'INSERT INTO `test` VALUES ($row'; for ($col = 0; $col query($query); }
OK, agora vamos verificar o uso de recursos:
como criar um plano de contas
// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo 'Before: ' . memory_get_peak_usage() . '
'; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo 'Limit 1: ' . memory_get_peak_usage() . '
'; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo 'Limit 10000: ' . memory_get_peak_usage() . '
';
Resultado:
Before: 224704 Limit 1: 224704 Limit 10000: 224704
Legal. Parece que a consulta é gerenciada com segurança internamente em termos de recursos.
Só para ter certeza, vamos aumentar o limite mais uma vez e defini-lo para 100.000. Opa. Quando fazemos isso, obtemos:
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
O que aconteceu?
O problema aqui é a maneira como o PHP mysql
módulo funciona. Na verdade, é apenas um proxy para libmysqlclient
, que faz o trabalho sujo. Quando uma parte dos dados é selecionada, ela vai diretamente para a memória. Como essa memória não é gerenciada pelo gerente do PHP, memory_get_peak_usage()
não mostrará nenhum aumento na utilização de recursos conforme aumentamos o limite em nossa consulta. Isso leva a problemas como o demonstrado acima, em que somos levados à complacência pensando que nosso gerenciamento de memória está bem. Mas, na realidade, nosso gerenciamento de memória apresenta falhas graves e podemos ter problemas como o mostrado acima.
Você pode pelo menos evitar o headfake acima (embora ele não melhore por si só a utilização da memória) usando mysqlnd
módulo. mysqlnd
é compilado como uma extensão PHP nativa e faz usar o gerenciador de memória do PHP.
Portanto, se executarmos o teste acima usando mysqlnd
em vez de mysql
, temos uma imagem muito mais realista de nossa utilização de memória:
Before: 232048 Limit 1: 324952 Limit 10000: 32572912
E é ainda pior do que isso, aliás. De acordo com a documentação do PHP, mysql
usa o dobro de recursos do que mysqlnd
para armazenar dados, então o script original usando mysql
realmente usou ainda mais memória do que mostrado aqui (aproximadamente o dobro).
Para evitar tais problemas, considere limitar o tamanho de suas consultas e usar um loop com pequeno número de iterações; por exemplo.:
$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i query( 'SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize'); }
Quando consideramos este erro do PHP e erro # 4 acima, percebemos que há um equilíbrio saudável que seu código idealmente precisa atingir entre, por um lado, ter suas consultas muito granulares e repetitivas e ter cada uma de suas consultas individuais muito grande. Como acontece com a maioria das coisas na vida, é necessário equilíbrio; ambos os extremos não são bons e podem causar problemas com o PHP não funcionando corretamente.
Em certo sentido, isso é realmente mais um problema no PHP em si do que algo que você enfrentaria ao depurar o PHP, mas nunca foi abordado de forma adequada. O núcleo do PHP 6 deveria ter reconhecimento de Unicode, mas isso foi colocado em espera quando o desenvolvimento do PHP 6 foi suspenso em 2010.
Mas isso de forma alguma isenta o desenvolvedor de manuseando corretamente UTF-8 e evitando a suposição errônea de que todas as strings serão necessariamente “ASCII simples e antigas”. Código que falha em lidar adequadamente com strings não ASCII é notório por apresentar gnarly heisenbugs em seu código. Mesmo simples strlen($_POST['name'])
ligações poderiam causar problemas se alguém com um sobrenome como “Schrödinger” tentasse se inscrever em seu sistema.
Aqui está uma pequena lista de verificação para evitar esses problemas em seu código:
mb_*
funções em vez das funções de string antigas (certifique-se de que a extensão “multibyte” esteja incluída em sua construção PHP).latin1
por padrão).json_encode()
converte símbolos não ASCII (por exemplo, “Schrödinger” torna-se “Schr u00f6dinger”), mas serialize()
faz não .Um recurso particularmente valioso a esse respeito é o UTF-8 Primer para PHP e MySQL postagem por Francisco claria neste blog.
$_POST
sempre conterá seus dados POSTApesar do nome, o $_POST
array nem sempre conterá seus dados POST e pode ser facilmente encontrado vazio. Para entender isso, vamos dar uma olhada em um exemplo. Suponha que fazemos uma solicitação de servidor com um jQuery.ajax()
ligue da seguinte forma:
// js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
(A propósito, observe o contentType: 'application/json'
aqui. Enviamos dados como JSON, que é bastante popular para APIs. É o padrão, por exemplo, para postar no AngularJS $http
serviço .)
No lado do servidor de nosso exemplo, simplesmente despejamos o $_POST
array:
// php var_dump($_POST);
Surpreendentemente, o resultado será:
array(0) { }
Por quê? O que aconteceu com nossa string JSON {a: 'a', b: 'b'}
?
A resposta é que O PHP só analisa uma carga útil POST automaticamente quando ele tem um tipo de conteúdo de application/x-www-form-urlencoded
ou multipart/form-data
. As razões para isso são históricas - esses dois tipos de conteúdo eram essencialmente os únicos usados anos atrás quando o PHP $_POST
foi implementado. Assim, com qualquer outro tipo de conteúdo (mesmo aqueles que são bastante populares hoje, como application/json
), o PHP não carrega automaticamente a carga útil do POST.
Desde $_POST
é uma superglobal, se substituirmos uma vez (de preferência no início do nosso script), o valor modificado (ou seja, incluindo a carga útil do POST) será referenciável em todo o nosso código. Isso é importante porque $_POST
é comumente usado por estruturas de PHP e quase todos os scripts personalizados para extrair e transformar dados de solicitação.
Portanto, por exemplo, ao processar uma carga útil POST com um tipo de conteúdo de application/json
, precisamos analisar manualmente o conteúdo da solicitação (ou seja, decodificar os dados JSON) e substituir o $_POST
variável, da seguinte forma:
// php $_POST = json_decode(file_get_contents('php://input'), true);
Então, quando despejamos o $_POST
array, vemos que inclui corretamente a carga útil do POST; por exemplo.:
array(2) { ['a']=> string(1) 'a' ['b']=> string(1) 'b' }
Veja este exemplo de código e tente adivinhar o que será impresso:
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . '
'; }
Se você respondeu de 'a' a 'z', pode se surpreender ao saber que estava errado.
Sim, vai imprimir de 'a' a 'z', mas depois Além disso imprima de 'aa' a 'yz'. Vamos ver por quê.
No PHP não há char
tipo de dados; apenas string
está disponível. Com isso em mente, incrementando o string
z
em PHP produz aa
:
php> $c = 'z'; echo ++$c . '
'; aa
No entanto, para confundir ainda mais as coisas, aa
é lexicograficamente Menos do que z
:
php> var_export((boolean)('aa' <'z')) . '
'; true
É por isso que o código de amostra apresentado acima imprime as letras a
a z
, mas então Além disso impressões aa
por meio de yz
. Ele para quando atinge za
, que é o primeiro valor que encontra que é 'maior que' z
:
php> var_export((boolean)('za' <'z')) . '
'; false
Sendo esse o caso, aqui está uma maneira de devidamente percorrer os valores ‘a’ a ‘z’ em PHP:
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . '
'; }
Ou alternativamente:
$letters = range('a', 'z'); for ($i = 0; $i Erro comum nº 9: Ignorar os padrões de codificação
Embora ignorar os padrões de codificação não leve diretamente à necessidade de depurar o código PHP, ainda é provavelmente uma das coisas mais importantes para discutir aqui.
Ignorar os padrões de codificação pode causar uma série de problemas em um projeto. Na melhor das hipóteses, resulta em um código inconsistente (já que cada desenvolvedor está “fazendo suas próprias coisas”). Mas, na pior das hipóteses, ele produz código PHP que não funciona ou pode ser difícil (às vezes quase impossível) de navegar, tornando-o extremamente difícil de depurar, aprimorar e manter. E isso significa produtividade reduzida para sua equipe, incluindo muito esforço desperdiçado (ou pelo menos desnecessário).
Felizmente para desenvolvedores de PHP, existe a recomendação de padrões PHP (PSR), composta pelos cinco padrões a seguir:
- PSR-0 : Padrão de carregamento automático
- PSR-1 : Padrão Básico de Codificação
- PSR-2 : Guia de estilo de codificação
- PSR-3 : Interface do Logger
- PSR-4 : Autoloader
O PSR foi originalmente criado com base em informações de mantenedores das plataformas mais reconhecidas do mercado. Zend, Drupal, Symfony, Joomla e outras contribuíram para esses padrões e agora os estão seguindo. Até o PEAR, que tentou ser um padrão durante anos antes disso, agora participa do PSR.
Em certo sentido, quase não importa qual é o seu padrão de codificação, contanto que você concorde com um padrão e o cumpra, mas seguir o PSR geralmente é uma boa ideia, a menos que você tenha algum motivo convincente em seu projeto para fazer o contrário . Mais e mais equipes e projetos estão se adaptando ao PSR. Tt é definitivamente reconhecido neste ponto como 'o' padrão pela maioria dos desenvolvedores de PHP, então usá-lo ajudará a garantir que os novos desenvolvedores estejam familiarizados e confortáveis com seu padrão de codificação quando se juntarem à sua equipe.
Erro comum nº 10: Uso incorreto empty()
Alguns desenvolvedores de PHP gostam de usar empty()
para verificações booleanas de quase tudo. Existem casos, porém, em que isso pode levar à confusão.
Primeiro, vamos voltar aos arrays e ArrayObject
instâncias (que imitam arrays). Dada sua similaridade, é fácil supor que matrizes e ArrayObject
as instâncias se comportarão de forma idêntica. Isso prova, no entanto, ser uma suposição perigosa. Por exemplo, no PHP 5.0:
// PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?
E para piorar ainda mais as coisas, os resultados teriam sido diferentes antes do PHP 5.0:
// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
Infelizmente, essa abordagem é bastante popular. Por exemplo, esta é a maneira ZendDbTableGateway
do Zend Framework 2 retorna dados ao chamar current()
em TableGateway::select()
resultado como o doc sugere. O desenvolvedor pode facilmente se tornar vítima desse erro com esses dados.
Para evitar esses problemas, a melhor abordagem para verificar se há estruturas de matriz vazias é usar count()
:
// Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)
E, aliás, uma vez que o PHP lança 0
para false
, count()
também pode ser usado em if ()
condições para verificar se há matrizes vazias. Também é importante notar que, em PHP, count()
é a complexidade constante (O(1)
operação) em matrizes, o que torna ainda mais claro que é a escolha certa.
Outro exemplo quando empty()
pode ser perigoso quando combinada com a função de classe mágica __get()
. Vamos definir duas classes e ter um test
propriedade em ambos.
Primeiro, vamos definir um Regular
classe que inclui test
como uma propriedade normal:
class Regular { public $test = 'value'; }
Então, vamos definir um Magic
classe que usa a magia __get()
operador para acessar seu test
propriedade:
class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
OK, agora vamos ver o que acontece quando tentamos acessar o test
propriedade de cada uma dessas classes:
$regular = new Regular(); var_dump($regular->test); // outputs string(4) 'value' $magic = new Magic(); var_dump($magic->test); // outputs string(4) 'value'
Tudo bem até agora.
Mas agora vamos ver o que acontece quando chamamos empty()
em cada um destes:
var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
Ugh. Portanto, se confiarmos em empty()
, podemos ser enganados e acreditar que test
propriedade de $magic
está vazio, enquanto na realidade está definido como 'value'
.
Infelizmente, se uma classe usa a magia __get()
função para recuperar o valor de uma propriedade, não há maneira infalível de verificar se o valor da propriedade está vazio ou não. Fora do escopo da classe, você só pode realmente verificar se um null
valor será retornado, e isso não significa necessariamente que a chave correspondente não esteja definida, pois na verdade poderia têm estado conjunto para null
.
inteligência de negócios vs inteligência artificial
Em contraste, se tentarmos fazer referência a uma propriedade não existente de um Regular
instância de classe, obteremos um aviso semelhante ao seguinte:
Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0
Portanto, o ponto principal aqui é que empty()
método deve ser usado com cuidado, pois pode levar a resultados confusos - ou mesmo potencialmente enganosos - se não for cuidadoso.
Embrulhar
A facilidade de uso do PHP pode acalmar os desenvolvedores com uma falsa sensação de conforto, deixando-os vulneráveis a longas depurações de PHP devido a algumas das nuances e idiossincrasias da linguagem. Isso pode resultar no não funcionamento do PHP e em problemas como os descritos aqui.
A linguagem PHP evoluiu significativamente ao longo de seus 20 anos de história. Familiarizar-se com suas sutilezas é um esforço que vale a pena, pois ajudará a garantir que o software que você produz é mais escalável, robusto e sustentável.