Doppelte Array-Schlüssel (Hinweis: Die Mitgliedsvariable "a" wurde mehrmals von __sleep () zurückgegeben.)

8

Der Titel mag ein bisschen albern erscheinen, aber ich meine das total ernst. Heute bei der Arbeit stieß ich auf ein seltsames PHP-Verhalten, das ich nicht erklären konnte. Glücklicherweise ist dieses Verhalten in PHP 7.4 behoben, so dass anscheinend auch jemand darauf gestoßen ist.

Ich habe ein kleines Beispiel gemacht, um zu veranschaulichen, was schief gelaufen ist:

<?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);

Führen Sie diesen Code hier aus: https://3v4l.org/DBt3o

Hier ist eine kleine Erklärung, was hier los ist. Wir müssen Klassen A und B, die beide eine Eigenschaft teilen $a. Sorgfältige Leser bemerkten, dass die Immobilie $azwei verschiedene Sichtbarkeiten hat (öffentlich, privat). Bisher nichts Besonderes. Die Magie geschieht in der __sleepMethode, die magisch aufgerufen wird, wenn wir serializeunsere Instanz. Wir wollen alle Objektvariablen, die wir erhalten get_object_vars, auf nur die Schlüssel mit reduzieren array_keysund alles mit ausgeben var_dump.

Ich würde so etwas erwarten (dies geschieht seit PHP 7.4 und ist meine erwartete Ausgabe):

array(2) {
  [0]=>
  string(1) "b"
  [1]=>
  string(1) "a"
}

Aber was ich bekomme ist folgendes:

array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "a"
}

Wie könnte es sein, dass PHP ein Array mit zwei völlig identischen Schlüsseln liefert? Wer kann erklären, was hier intern passiert, weil ich in einfachem PHP kein Array mit zwei völlig identischen Schlüsseln generieren kann? Oder vermisse ich hier etwas Offensichtliches?

Meine Mitarbeiter wollten mir zunächst nicht glauben, aber keiner von ihnen hatte eine gute Erklärung dafür, nachdem sie verstanden hatten, was hier passiert.

Ich würde wirklich gerne eine gute Erklärung sehen.

Benjamin Paap
quelle
1
Es ist interessant, wenn Sie die Zeile aufvar_dump(array_keys((array)$this));
Nigel Ren
Ich habe eine Antwort gegeben, habe sie aber inzwischen entfernt, da ich jetzt der Meinung bin, dass dieser Auszug aus dem PHP-Handbuch "Ruft die zugänglichen nicht statischen Eigenschaften des angegebenen Objekts je nach Umfang ab" ein einfacher Fehler ist. Ich sage dies, weil die private Ahneneigenschaft $ a für B nicht "zugänglich" ist. Ich nahm an, dass dieses Ergebnis möglicherweise darauf zurückzuführen ist, dass Sie sich in A :: __ sleep auf $ this beziehen und es somit den vollen Umfang aller zeigt, jedoch mit verschob es in B :: __ Schlaf, das Verhalten bleibt identisch.
Pancho

Antworten:

6

Ich konnte keinen Bericht für den Fehler in der Frage finden, aber interessanterweise scheint dieses Commit dasselbe zu behandeln:

Wenn wir uns in einem Bereich befinden, in dem das schattierte Privateigentum sichtbar ist, sollte das schattierende öffentliche Eigentum nicht sichtbar sein.

Der Testcode ist gut geschrieben, mit einer einfachen Änderung könnten wir ihn hier haben:

class Test
{
    private $prop = "Test";

    function run()
    {
        return get_object_vars($this);
    }
}

class Test2 extends Test
{
    public $prop = "Test2";
}

$props = (new Test2)->run();

Aufruf var_dump()zu $propsShows:

array(2) {
  ["prop"]=>
  string(5) "Test2"
  ["prop"]=>
  string(4) "Test"
}

Zurück zu Ihrer Frage:

Wie könnte es sein, dass PHP ein Array mit zwei völlig identischen Schlüsseln liefert? Wer kann erklären, was hier intern passiert, weil ich in einfachem PHP kein Array mit zwei völlig identischen Schlüsseln generieren kann?

Ja, Sie können kein Array mit zwei identischen Schlüsseln haben:

var_dump(array_flip(array_flip($props)));

Ergebnisse in:

array(1) {
  ["prop"]=>
  string(4) "Test"
}

Aber lassen Sie mich Ihnen nicht zustimmen, two completely identical keysda diese beiden Elemente mit identischen Schlüsselnamen nicht intern mit identischen Schlüsseln in einer Hashtabelle gespeichert sind. Das heißt, diese werden außer bei möglichen Kollisionen als eindeutige Ganzzahlen gespeichert, und da dies intern aufgetreten ist, wurde die Einschränkung der Benutzereingaben ignoriert.

revo
quelle
3

Nachdem Sie ein wenig damit herumgespielt haben, sieht es so aus, als ob dies nicht davon abhängt __sleep().

Anscheinend war dies in früheren Versionen von PHP 7 immer der Fall (aber anscheinend nicht in PHP 5). Dieses kleinere Beispiel zeigt das gleiche Verhalten.

class A {
    private $a = 'This is $a from A';

    public function showProperties() { return get_object_vars($this); }
}

class B extends A
{
    public $a = 'This is $a from B';
}

$b = new B;
var_dump($b->showProperties());

Ausgabe von PHP 7.0 - 7.3

array(2) {
  ["a"]=>
  string(17) "This is $a from B"
  ["a"]=>
  string(17) "This is $a from A"
}

Ich denke, das Private $aim Elternteil ist eine andere Eigenschaft als das Öffentliche $aim Kind. Wenn Sie die Sichtbarkeit in ändern, ändern BSie nicht die Sichtbarkeit des $aIn A, sondern erstellen wirklich eine neue Eigenschaft mit demselben Namen. Wenn Sie var_dumpdas Objekt selbst sind, können Sie beide Eigenschaften sehen.

Es sollte jedoch keine großen Auswirkungen haben, da Sie nicht über die übergeordnete Klasse in der untergeordneten Klasse auf die private Eigenschaft zugreifen können, obwohl Sie sehen können, dass sie in diesen früheren PHP 7-Versionen vorhanden ist.

Keine Panik
quelle
1
Sollte nicht möglich sein, dass sich das assoziative Array (Hash-Tabelle) in diesem Zustand befindet. Zugänglich ist nur einer von ihnen, aber die Größe ist 2.
Weltschmerz
@Weltschmerz da stimme ich zu. Es sieht wirklich komisch aus.
Keine Panik
2
Der Zugriff auf den Index agibt auch den zweiten zurück This is $a from A.
AbraCadaver
@AbraCadaver Das habe ich auch bemerkt. Ich nehme an, dieser Teil ist sinnvoll, da Sie den letzten Wert erhalten, wenn Sie ein Array-Literal mit doppelten Schlüsseln schreiben.
Keine Panik
0

Mein paar Cent.

Ich weiß nichts über Mitarbeiter, aber ich habe nicht geglaubt und dachte, das sei ein Witz.

Zur Erklärung: Das Problem befindet sich definitiv unter der Variablen "get_object_vars", da es ein doppeltes assoziatives Array zurückgibt. Sollte zwei verschiedene Hash-Tabellenwerte für denselben Schlüssel sein (was nicht möglich ist, aber die einzige Erklärung kommt). Ich konnte keine Links zur internen Implementierung von get_object_vars () finden (obwohl PHP auf Open Source basiert, so dass es möglich ist, Code abzurufen und irgendwie zu debuggen). Außerdem denke ich (bisher erfolglos) darüber nach, wie ich die Array-Darstellung im Speicher einschließlich der Hash-Tabelle sehen kann. Auf der anderen Seite konnte ich PHP "legale" Funktionen verwenden und einige Tricks mit Array machen.

Dies ist mein Versuch, einige Funktionen mit diesem assoziativen Array zu testen. Unten ist die Ausgabe. Keine Erklärung erforderlich - Sie können alles sehen und den gleichen Code selbst ausprobieren, also nur einige Kommentare.

  1. Meine Umgebung ist PHP 7.2.12 x86 (32 Bit) - na ja ... schade um mich

  2. Ich werde "Magie" und Serialisierung los und habe nur Dinge übrig gelassen, die Probleme verursachen.

  3. Einige Umgestaltungen der Klassen A und B sowie des Funktionsaufrufs wurden abgeschlossen.

  4. Der $ Schlüssel unter Klasse A muss privat sein, sonst kein Wunder.

  5. Teiletests vars - nichts Interessantes außer dem Hauptproblem.

  6. Teiletest copy_vars - Array wurde mit Duplikat kopiert !! Neuer Schlüssel wurde erfolgreich hinzugefügt.

  7. Teiletestiteration und new_vars - Die Iteration wurde problemlos dupliziert, aber das neue Array akzeptierte kein Duplikat, der letzte Schlüssel wurde akzeptiert.

  8. Testersatz - Austausch beim zweiten Schlüssel abgeschlossen, doppelter Aufenthalt.

  9. Das Testen von ksort - array hat sich nicht geändert, Duplikate wurden nicht erkannt

  10. Testen von asort - Nachdem ich Werte geändert und asort ausgeführt hatte, konnte ich die Reihenfolge ändern und doppelte Schlüssel austauschen. Jetzt wird der erste Schlüssel zum zweiten und der neue Schlüssel ist derjenige, wenn wir Array für Schlüssel aufrufen oder einen Schlüssel zuweisen. Infolgedessen konnte ich beide Schlüssel ändern !! Bevor ich dachte, dass ein doppelter Schlüssel unsichtbar ist, ist jetzt klar, dass der letzte Schlüssel funktioniert, wenn wir auf den Schlüssel verweisen oder ihn zuweisen.

  11. Konvertierung in ein stdClass-Objekt - auf keinen Fall! Nur letzter Schlüssel akzeptiert!

  12. Testen auf Unset - gute Arbeit! Letzter Schlüssel entfernt, aber der erste Schlüssel ist verantwortlich und der einzige Schlüssel übrig, keine Duplikate.

  13. Interner Repräsentationstest - Dies ist ein Thema, um einige andere Funktionen hinzuzufügen und die Quelle der Duplizierung zu ermitteln. Ich denke jetzt darüber nach.

Die Ergebnisausgabe befindet sich unterhalb des Codes.

<?php

class A {
    private $key = 'This is $a from A';

    protected function funcA() {
        $vars = get_object_vars($this);

        return $vars;
    }
}

class B extends A
{
    public $key = 'This is $a from B';

    public function funcB() {
        return $this->funcA();
    }
}

$b = new B();

$vars = $b->funcB();

echo "testing vars:\n\n\n";

var_dump($vars);
var_dump($vars['key']);
var_dump(array_keys($vars));

echo "\n\n\ntesting copy_vars:\n\n\n";

$copy_vars = $vars;
$copy_vars['new_key'] = 'this is a new key';

var_dump($vars);
var_dump($copy_vars);

echo "\n\n\ntesting iteration and new_vars:\n\n\n";

$new_vars = [];
foreach($vars as $key => $val) {
    echo "adding '$key', '$val'\n";
    $new_vars[$key] = $val;
}

var_dump($new_vars);

echo "\n\n\ntesting replace key (for copy):\n\n\n";

var_dump($copy_vars);
$copy_vars['key'] = 'new key';
var_dump($copy_vars);

echo "\n\n\ntesting key sort (for copy):\n\n\n";

var_dump($copy_vars);
ksort($copy_vars);
var_dump($copy_vars);

echo "\n\n\ntesting asort (for copy):\n\n\n";

$copy_vars['key'] = "A - first";
var_dump($copy_vars);
asort($copy_vars);
var_dump($copy_vars);
$copy_vars['key'] = "Z - last";
var_dump($copy_vars);

echo "\n\n\ntesting object conversion (for copy):\n\n\n";

var_dump($copy_vars);
$object = json_decode(json_encode($copy_vars), FALSE);
var_dump($object);


echo "\n\n\ntesting unset (for copy):\n\n\n";

var_dump($copy_vars);
unset($copy_vars['key']);
var_dump($copy_vars);


echo "\n\n\ntesting inernal representation:\n\n\n";

debug_zval_dump($vars);

Jetzt ausgeben:

testing vars:


array(2) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(17) "This is $a from A"
}
string(17) "This is $a from A"
array(2) {
  [0]=>
  string(3) "key"
  [1]=>
  string(3) "key"
}



testing copy_vars:


array(2) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(17) "This is $a from A"
}
array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(17) "This is $a from A"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing iteration and new_vars:


adding 'key', 'This is $a from B'
adding 'key', 'This is $a from A'
array(1) {
  ["key"]=>
  string(17) "This is $a from A"
}



testing replace key (for copy):


array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(17) "This is $a from A"
  ["new_key"]=>
  string(17) "this is a new key"
}
array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(7) "new key"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing key sort (for copy):


array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(7) "new key"
  ["new_key"]=>
  string(17) "this is a new key"
}
array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(7) "new key"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing asort (for copy):


array(3) {
  ["key"]=>
  string(17) "This is $a from B"
  ["key"]=>
  string(9) "A - first"
  ["new_key"]=>
  string(17) "this is a new key"
}
array(3) {
  ["key"]=>
  string(9) "A - first"
  ["key"]=>
  string(17) "This is $a from B"
  ["new_key"]=>
  string(17) "this is a new key"
}
array(3) {
  ["key"]=>
  string(9) "A - first"
  ["key"]=>
  string(8) "Z - last"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing object conversion (for copy):


array(3) {
  ["key"]=>
  string(9) "A - first"
  ["key"]=>
  string(8) "Z - last"
  ["new_key"]=>
  string(17) "this is a new key"
}
object(stdClass)#2 (2) {
  ["key"]=>
  string(8) "Z - last"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing unset (for copy):


array(3) {
  ["key"]=>
  string(9) "A - first"
  ["key"]=>
  string(8) "Z - last"
  ["new_key"]=>
  string(17) "this is a new key"
}
array(2) {
  ["key"]=>
  string(9) "A - first"
  ["new_key"]=>
  string(17) "this is a new key"
}



testing inernal representation:


array(2) refcount(2){
  ["key"]=>
  string(17) "This is $a from B" refcount(2)
  ["key"]=>
  string(17) "This is $a from A" refcount(4)
}
Anatoliy R.
quelle