O título pode parecer um pouco bobo, mas eu sou totalmente sério com isso. Hoje no trabalho, me deparei com um comportamento estranho do PHP que não consegui explicar. Felizmente, esse comportamento foi corrigido no PHP 7.4, então parece que alguém se deparou com isso também.
Fiz um pequeno exemplo para ilustrar o que deu errado:
<?php
class A {
private $a = 'This is $a from A';
public $b = 'This is $b from A';
public function __sleep(): array
{
var_dump(array_keys(get_object_vars($this)));
return [];
}
}
class B extends A
{
public $a = 'This is $a from B';
}
$b = new B;
serialize($b);
Execute este código aqui: https://3v4l.org/DBt3o
Aqui está uma pequena explicação do que está acontecendo aqui. Temos as classes A e B, que compartilham uma propriedade $a
. Os leitores cuidadosos notaram que a propriedade $a
tem duas visibilidades diferentes (pública, privada). Nada extravagante até agora. A mágica acontece no __sleep
método que é chamado magicamente quando usamos serialize
nossa instância. Queremos ter todas as variáveis de objeto com as quais get_object_vars
reduzimos isso apenas para as chaves array_keys
e produzimos tudo com var_dump
.
Eu esperaria algo assim (isso acontece desde o PHP 7.4 e é a minha saída esperada):
array(2) {
[0]=>
string(1) "b"
[1]=>
string(1) "a"
}
Mas o que eu recebo é o seguinte:
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "a"
}
Como poderia ser, que o PHP oferece uma matriz com duas chaves completamente idênticas? Quem é capaz de explicar o que acontece aqui internamente porque, em PHP simples, não sou capaz de gerar uma matriz com duas chaves completamente idênticas? Ou eu sinto falta de algo óbvio aqui?
Meus colegas de trabalho não quiseram acreditar em mim a princípio, mas nenhum deles teve uma boa explicação disso depois de entenderem o que está acontecendo aqui.
Eu realmente adoraria ver uma boa explicação.
fonte
var_dump(array_keys((array)$this));
Respostas:
Não consegui encontrar um relatório para o bug na pergunta, mas curiosamente parece que esse commit aborda a mesma coisa:
O código de teste está bem escrito, com uma simples alteração, podemos tê-lo aqui:
Chamando
var_dump()
na$props
mostra:Voltar à sua pergunta:
Sim, você não pode ter uma matriz com duas chaves idênticas:
resulta em:
mas não permita que você concorde com você,
two completely identical keys
pois esses dois elementos com nomes de chave idênticos não são armazenados internamente com chaves idênticas em uma hashtable. Ou seja, eles são armazenados como números inteiros exclusivos, exceto em possíveis colisões e, como isso ocorre internamente, a restrição nas entradas do usuário foi ignorada.fonte
Depois de mexer um pouco com isso, parece que isso não depende
__sleep()
.Aparentemente, esse sempre foi o caso nas versões anteriores do PHP 7 (mas aparentemente não no PHP 5). Este exemplo menor mostra o mesmo comportamento.
Saída do PHP 7.0 - 7.3
Eu acho que o privado
$a
nos pais é uma propriedade diferente do público$a
na criança. Quando você alterar a visibilidade emB
você não está alterando a visibilidade do$a
noA
, você está realmente fazendo uma nova propriedade com o mesmo nome. Se você forvar_dump
o próprio objeto, poderá ver as duas propriedades.Porém, não deve ter muito efeito, já que você não seria capaz de acessar a propriedade privada da classe pai na classe filho, mesmo que você possa ver que ela existe nessas versões anteriores do PHP 7.
fonte
a
retorna o segundoThis is $a from A
.Meus centavos.
Não conheço colegas de trabalho, mas não acreditei e pensei que isso fosse uma piada.
Para a explicação - definitivamente o problema está na variável "get_object_vars", pois está retornando um array associativo duplicado. Deve haver dois valores diferentes de tabela de hash para a mesma chave (o que não é possível, mas a única explicação vem). Não consegui encontrar nenhum link para a implementação interna de get_object_vars () (embora o PHP seja baseado em código aberto, é possível obter código e depurar de alguma forma). Também estou pensando (sem êxito até agora) no caminho para ver a representação da matriz na memória, incluindo tabela de hash. Por outro lado, eu era capaz de usar funções "legais" do PHP e fazer alguns truques com o array.
Esta é minha tentativa de testar algumas funcionalidades com essa matriz associativa. Abaixo está a saída. Nenhuma explicação é necessária - você pode ver tudo e experimentar o mesmo código, portanto, apenas alguns comentários.
Meu ambiente é php 7.2.12 x86 (32 bits) - bem ... sim, que pena!
Eu me livrei da "mágica" e da serialização, deixei apenas coisas trazendo problemas.
Concluiu algumas refatorações nas classes A e B, bem como na chamada de função.
A chave $ na classe A deve ser privada, caso contrário não será um milagre.
Vars de teste de peças - nada de interessante, exceto o problema principal.
Teste de peça copy_vars - o array foi copiado com duplicado !! Nova chave foi adicionada com sucesso.
Iteração de teste de peça e new_vars - a iteração foi duplicada sem problemas, mas a nova matriz não aceitou a última chave duplicada e aceita.
Substituição de teste - substituição concluída na segunda chave, estadia duplicada.
Testando o ksort - o array não mudou, o duplicado não foi reconhecido
Testando o asort - após alterar os valores e executar o asort, fui capaz de alterar a ordem e trocar chaves duplicadas. Agora, a primeira chave se torna a segunda e a nova chave é a que chamamos de matriz por chave ou atribuímos uma chave. Como resultado, consegui alterar as duas chaves !! Antes que eu pensasse que a chave duplicada é meio invisível, agora está claro que a última chave funciona quando fazemos referência ou atribuímos a chave.
Conversão para objeto stdClass - de jeito nenhum! Somente a última chave aceita!
Testando quanto à falta de ajuste - bom trabalho! Última chave removida, mas a primeira chave está no comando e a única chave restante, sem duplicatas.
Teste de representação interna - este é um assunto para adicionar outras funções e ver a fonte da duplicação. Estou pensando nisso agora.
A saída do resultado está abaixo do código.
Saída agora:
fonte