Các khóa mảng trùng lặp (Lưu ý: biến thành viên, một tên lửa được trả về từ __s ngủ () nhiều lần)


8

Tiêu đề có vẻ hơi ngớ ngẩn nhưng tôi hoàn toàn nghiêm túc với điều này. Hôm nay tại nơi làm việc tôi đã bắt gặp một hành vi kỳ lạ của PHP mà tôi không thể giải thích. May mắn thay, hành vi này được sửa trong PHP 7.4, vì vậy dường như ai đó cũng vấp phải điều đó.

Tôi đã làm một ví dụ nhỏ để minh họa những gì đã sai:

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

Chạy mã này tại đây: https://3v4l.org/DBt3o

Đây là một lời giải thích về những gì đang xảy ra ở đây. Chúng ta phải học lớp A và B, cả hai đều có chung một tài sản $a. Độc giả cẩn thận nhận thấy rằng tài sản $acó hai tầm nhìn khác nhau (công khai, riêng tư). Không có gì lạ mắt cho đến nay. Phép thuật xảy ra trong __sleepphương thức được gọi một cách kỳ diệu khi chúng serializeta ví dụ. Chúng tôi muốn có tất cả các biến đối tượng mà chúng tôi nhận được với việc get_object_varsgiảm chỉ còn các khóa với array_keysvà xuất mọi thứ với var_dump.

Tôi mong đợi một cái gì đó như thế này (điều này xảy ra kể từ PHP 7.4 và là đầu ra mong đợi của tôi):

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

Nhưng những gì tôi nhận được là đây:

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

Làm thế nào có thể, PHP cung cấp một mảng với hai khóa hoàn toàn giống nhau? Ai có thể giải thích những gì xảy ra ở đây bởi vì trong PHP đơn giản, tôi không thể tạo ra một mảng với hai khóa hoàn toàn giống nhau? Hay tôi bỏ lỡ một cái gì đó rõ ràng ở đây?

Đồng nghiệp của tôi lúc đầu không muốn tin tôi nhưng không ai trong số họ có lời giải thích tốt về điều này sau khi họ hiểu chuyện gì đang xảy ra ở đây.

Tôi thực sự rất thích nhìn thấy một lời giải thích tốt.


1
Thật thú vị nếu bạn đổi dòng thànhvar_dump(array_keys((array)$this));
Nigel Ren

Tôi đã đưa ra một câu trả lời nhưng đã loại bỏ nó bởi vì bây giờ tôi nghĩ rằng đã đưa ra đoạn trích này từ hướng dẫn sử dụng PHP "Nhận các thuộc tính không tĩnh có thể truy cập của đối tượng đã cho theo phạm vi." Đây là một lỗi đơn giản. Tôi nói điều này bởi vì thuộc tính tổ tiên riêng $ a không "có thể truy cập" được B. Tôi cho rằng kết quả này có thể là do bạn đề cập đến $ this trong A :: __ và do đó hiển thị toàn bộ phạm vi của tất cả, tuy nhiên có chuyển nó sang B :: __ ngủ, hành vi vẫn giống hệt.
Pancho

Câu trả lời:


6

Tôi không thể tìm thấy một báo cáo về lỗi trong câu hỏi nhưng thú vị là có vẻ như cam kết này giải quyết điều tương tự:

Nếu chúng ta ở trong một phạm vi mà tài sản riêng bị che khuất có thể nhìn thấy, thì tài sản công cộng bị che khuất sẽ không hiển thị.

Mã kiểm tra được viết tốt, với một thay đổi đơn giản chúng ta có thể có nó ở đây:

class Test
{
    private $prop = "Test";

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

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

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

Gọi var_dump()về $propschương trình:

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

Quay lại câu hỏi của bạn:

Làm thế nào có thể, PHP cung cấp một mảng với hai khóa hoàn toàn giống nhau? Ai có thể giải thích những gì xảy ra ở đây bởi vì trong PHP đơn giản, tôi không thể tạo ra một mảng với hai khóa hoàn toàn giống nhau?

Có, bạn không thể có một mảng với hai khóa giống nhau:

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

kết quả trong:

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

nhưng hãy để tôi không đồng ý với bạn two completely identical keysvì hai yếu tố này có tên khóa giống hệt nhau không được lưu trữ với các khóa giống hệt nhau bên trong một hàm băm. Đó là, những thứ đó được lưu trữ dưới dạng các số nguyên duy nhất ngoại trừ các xung đột tiềm ẩn và vì điều này đã xảy ra trong nội bộ, các hạn chế về đầu vào của người dùng đã bị bỏ qua.


3

Sau khi làm hỏng điều này một chút, có vẻ như điều này không phụ thuộc vào __sleep() .

Rõ ràng đây luôn là trường hợp trong các phiên bản trước của PHP 7 (nhưng dường như không phải trong PHP 5). Ví dụ nhỏ hơn này cho thấy hành vi tương tự.

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

Đầu ra từ PHP 7.0 - 7.3

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

Tôi nghĩ rằng sự riêng tư $atrong cha mẹ là một tài sản khác với công chúng $aở trẻ em. Khi bạn thay đổi mức độ hiển thị trong Bkhi bạn không thay đổi mức độ hiển thị của bên $atrong A, bạn thực sự tạo một tài sản mới có cùng tên. Nếu bạn var_dumplà đối tượng chính nó, bạn có thể thấy cả hai thuộc tính.

Mặc dù vậy, nó không có nhiều tác dụng, vì bạn sẽ không thể truy cập tài sản riêng từ lớp cha trong lớp con, mặc dù bạn có thể thấy nó tồn tại trong các phiên bản PHP 7 trước đó.


1
Không nên có khả năng mảng kết hợp (bảng băm) ở trạng thái này. Có thể truy cập chỉ là một trong số họ, nhưng kích thước là 2.
Weltschmerz

@Weltschmerz Tôi đồng ý. Nó trông thật kỳ lạ.
Đừng hoảng sợ

2
Ngoài ra, truy cập chỉ mục atrả về cái thứ hai This is $a from A.
AbraCadaver

@AbraCadaver Tôi cũng nhận thấy điều đó. Phần đó có ý nghĩa tôi cho rằng, vì bạn sẽ kết thúc với giá trị cuối cùng khi bạn viết một mảng bằng chữ với các khóa trùng lặp.
Đừng hoảng sợ

0

Đôi xu của tôi.

Tôi không biết về đồng nghiệp, nhưng tôi không tin và nghĩ đây là một trò đùa.

Đối với lời giải thích - chắc chắn vấn đề nằm dưới biến "get_object_vars" vì nó trả về mảng kết hợp trùng lặp. Phải là hai giá trị bảng băm khác nhau cho cùng một khóa (điều này là không thể, nhưng giải thích duy nhất là có). Tôi không thể tìm thấy bất kỳ liên kết nào đến việc triển khai get_object_vars () nội bộ (mặc dù PHP dựa trên nguồn mở để có thể lấy mã và gỡ lỗi bằng cách nào đó). Ngoài ra tôi đang suy nghĩ (không thành công cho đến nay) trên đường để xem biểu diễn mảng trong bộ nhớ bao gồm bảng băm. Mặt khác, tôi đã có thể sử dụng các hàm "hợp pháp" của PHP và thực hiện một số thủ thuật với mảng.

Đây là nỗ lực của tôi để kiểm tra một số chức năng với mảng kết hợp đó. Dưới đây là đầu ra. Không cần giải thích - bạn có thể xem mọi thứ và tự mình thử mã, vì vậy chỉ có một số nhận xét.

  1. Môi trường của tôi là php 7.2.12 x86 (32 bit) - ờ ... ừ, xấu hổ với tôi

  2. Tôi thoát khỏi "ma thuật" và tuần tự hóa, chỉ còn lại những thứ mang lại vấn đề.

  3. Đã hoàn thành một số tái cấu trúc trên các lớp A và B cũng như gọi hàm.

  4. Khóa $ dưới lớp A phải là riêng tư, nếu không thì không có phép màu.

  5. Phần thử nghiệm vars - không có gì thú vị ngoại trừ vấn đề chính.

  6. Phần sao chép thử nghiệm - mảng đã được sao chép với bản sao !! Khóa mới đã được thêm thành công.

  7. Kiểm tra phần lặp và new_vars - lặp đi lặp lại mà không gặp sự cố, nhưng mảng mới không chấp nhận trùng lặp, khóa cuối cùng được chấp nhận.

  8. Kiểm tra thay thế - thay thế hoàn thành trên khóa thứ hai, trùng lặp ở lại.

  9. Kiểm tra ksort - mảng không thay đổi, trùng lặp không được công nhận

  10. Kiểm tra asort - sau khi thay đổi giá trị và chạy asort, tôi có thể thay đổi thứ tự và trao đổi các khóa trùng lặp. Bây giờ khóa đầu tiên trở thành khóa thứ hai và khóa mới là khóa khi chúng ta gọi mảng bằng phím hoặc gán khóa. Kết quả là tôi đã có thể thay đổi cả hai phím !! Trước đây tôi nghĩ rằng khóa trùng lặp là một loại vô hình, bây giờ rõ ràng khóa cuối cùng hoạt động khi chúng ta tham chiếu hoặc gán khóa.

  11. Chuyển đổi sang đối tượng stdClass - không có cách nào! Chỉ có chìa khóa cuối cùng được chấp nhận!

  12. Kiểm tra cho unset - công việc tốt! Khóa cuối cùng bị xóa, nhưng khóa đầu tiên phụ trách và chỉ còn lại một khóa, không trùng lặp.

  13. Kiểm tra biểu diễn bên trong - đây là một chủ đề để thêm một số chức năng khác và xem nguồn gốc của sao chép. Tôi đang nghĩ về điều đó bây giờ.

Kết quả đầu ra là dưới mã.

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

Đầu ra ngay bây giờ:

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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.