2011-12-01 30 views
10

Tôi đang tạo một hàm bao quanh mysqli để ứng dụng của tôi không cần quá phức tạp với mã xử lý cơ sở dữ liệu. Một phần trong số đó là một chút mã để tham số hóa các cuộc gọi SQL bằng cách sử dụng mysqli :: bind_param(). bind_param(), như bạn có thể biết, yêu cầu tham chiếu. Kể từ đó là một wrapper bán chung, tôi sẽ chỉ làm cho cuộc gọi này:Mảng tham chiếu PHP của tôi là "kỳ diệu" trở thành một mảng các giá trị ... tại sao?

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 

và tôi nhận được một thông báo lỗi:

Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given 

Các cuộc thảo luận ở trên là để Forstall những người sẽ nói "Bạn don' t cần tham khảo ở tất cả trong ví dụ của bạn ".

của tôi "thực" mã phức tạp hơn một chút so với bất cứ ai muốn đọc, vì vậy tôi đã luộc mã dẫn đến lỗi này như sau (hy vọng) Ví dụ minh họa:

class myclass { 
    private $myarray = array(); 

    function setArray($vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 
    function dumpArray() { 
    var_dump($this->myarray); 
    } 
} 

function myfunc($vals) { 
    $obj = new myclass; 
    $obj->setArray($vals); 
    $obj->dumpArray(); 
} 

myfunc(array('key1' => 'val1', 
      'key2' => 'val2')); 

Vấn đề dường như, trong myfunc(), ở giữa lời gọi hàm setArray() và lệnh gọi tới dumpArray(), tất cả các phần tử trong $ obj-> myarray sẽ không còn là tham chiếu và chỉ trở thành các giá trị. Điều này có thể dễ dàng nhìn thấy bằng cách nhìn vào đầu ra:

array(2) { 
    [0]=> 
    &string(4) "val1" 
    [1]=> 
    &string(4) "val2" 
} 
array(2) { 
    [0]=> 
    string(4) "val1" 
    [1]=> 
    string(4) "val2" 
} 

Lưu ý rằng mảng ở trạng thái "đúng" trong nửa đầu ra ở đây. Nếu nó có ý nghĩa để làm như vậy, tôi có thể thực hiện lệnh bind_param() tại thời điểm đó, và nó sẽ hoạt động. Thật không may, một cái gì đó phá vỡ trong nửa sau của đầu ra. Lưu ý việc thiếu "&" trên các loại giá trị mảng.

Điều gì đã xảy ra với các tham chiếu của tôi? Làm thế nào tôi có thể ngăn chặn điều này xảy ra? Tôi ghét gọi "lỗi PHP" khi tôi thực sự không phải là chuyên gia về ngôn ngữ, nhưng đây có phải là một chuyên gia không? Nó có vẻ rất kỳ quặc với tôi. Tôi đang sử dụng PHP 5.3.8 cho thử nghiệm của tôi vào lúc này.


Edit:

Như nhiều người đã chỉ ra, việc sửa chữa là thay đổi setArray() để chấp nhận lập luận của mình bằng cách tham khảo:

function setArray(&$vals) { 

tôi thêm ghi chú này để tài liệu TẠI SAO điều này dường như làm việc.

PHP nói chung và mysqli nói riêng, dường như có khái niệm hơi kỳ lạ về "tham chiếu" là gì. Quan sát ví dụ này:

$a = "foo"; 
$b = array(&$a); 
$c = array(&$a); 
var_dump($b); 
var_dump($c); 

Trước hết, tôi chắc chắn rằng bạn đang tự hỏi tại sao tôi đang sử dụng mảng thay vì các biến vô hướng - đó là vì var_dump() không hiển thị bất kỳ chỉ số cho biết một đại lượng vô hướng là một tham chiếu, nhưng nó cho các thành viên mảng.

Dù sao, tại thời điểm này, $ b [0] và $ c [0] đều là tham chiếu đến $ a. Càng xa càng tốt. Bây giờ, chúng tôi ném cờ lê đầu tiên của chúng tôi vào các công trình:

unset($a); 
var_dump($b); 
var_dump($c); 

$ b [0] và $ c [0] đều được tham chiếu đến cùng một thứ. Nếu chúng ta thay đổi một, cả hai sẽ vẫn thay đổi. Nhưng chúng là gì? Một số vị trí chưa đặt tên trong bộ nhớ. Tất nhiên, thu gom rác đảm bảo rằng dữ liệu của chúng tôi an toàn và sẽ vẫn như vậy, cho đến khi chúng tôi dừng tham chiếu đến nó.

Đối lừa tiếp theo của chúng tôi, chúng tôi làm điều này:

unset($b); 
var_dump($c); 

Bây giờ $ c [0] là tham chiếu duy nhất còn lại để dữ liệu của chúng tôi. Và, whoa! Một cách kỳ diệu, nó không còn là "tham chiếu" nữa. Không phải bởi var_dump() 's đo lường, và không phải bởi mysqli :: bind_param()' s biện pháp hoặc.

PHP manual nói rằng có một cờ riêng biệt 'is_ref' trên mọi phần dữ liệu. Tuy nhiên, thử nghiệm này dường như cho thấy rằng 'is_ref' thực sự là tương đương với '(refcount> 1)'

Đối với niềm vui, bạn có thể sửa đổi mẫu đồ chơi này như sau:

$a = array("foo"); 
$b = array(&$a[0]); 
$c = array(&$a[0]); 

var_dump($a); 
var_dump($b); 
var_dump($c); 

Lưu ý rằng cả ba Các mảng có dấu tham chiếu trên các thành viên của chúng, trong đó sao lưu ý tưởng rằng 'is_ref' có chức năng tương đương với '(refcount> 1)'. Đó là ngoài tôi lý do tại sao mysqli :: bind_param() sẽ quan tâm đến sự khác biệt này ở nơi đầu tiên (hoặc có lẽ nó là call_user_func_array() ... dù bằng cách nào), nhưng nó sẽ xuất hiện rằng những gì chúng tôi "thực sự" cần phải đảm bảo là số lượng tham chiếu tối thiểu là cho mỗi thành viên của $ this-> bindArgs trong cuộc gọi call_user_func_array() của chúng tôi (xem phần đầu của bài đăng/câu hỏi). Và cách dễ nhất để làm điều đó (trong trường hợp này) là tạo ra setArray() pass-by-reference.


Edit:

Đối với niềm vui và trò chơi, tôi sửa đổi chương trình ban đầu của tôi (không hiển thị ở đây) rời tương đương để setArray() pass-by-value, và để tạo thêm một mảng vu vơ , bindArgsCopy, chứa chính xác điều tương tự như bindArgs. Điều đó có nghĩa rằng, có, cả hai mảng chứa tham chiếu đến dữ liệu "tạm thời" được deallocated bởi thời gian của cuộc gọi thứ hai. Theo dự đoán của phân tích ở trên, điều này đã hiệu quả. Điều này chứng tỏ rằng các phân tích trên không phải là một tạo phẩm của các hoạt động bên trong của var_dump() (giảm nhẹ cho tôi, ít nhất), và nó cũng chứng minh rằng đó là số tham chiếu quan trọng, chứ không phải là "tạm thời" của bản gốc lưu trữ dữ liệu.

So. Tôi thực hiện xác nhận sau: Trong PHP, với mục đích của call_user_func_array() (và có thể nhiều hơn), nói rằng một mục dữ liệu là một "tham chiếu" là điều tương tự khi nói rằng số tham chiếu của mục lớn hơn hoặc bằng 2 (bỏ qua tối ưu hóa bộ nhớ trong của PHP cho vô hướng bằng giá trị)


lưu ý tinh quản trị: tôi rất muốn cho Mario tín dụng tại chỗ cho câu trả lời, vì ông là người đầu tiên đề xuất câu trả lời chính xác, nhưng kể từ khi ông đã viết nó trong một chú thích, không phải là một câu trả lời thực tế, tôi không thể làm điều đó :-(

+1

wouldn' phương thức 'setArray()' cũng cần lấy mảng như tham số tham chiếu? Khác bạn chỉ là tạo tham chiếu đến một mảng tạm thời. – mario

+0

@mario: Bạn, thưa bạn, là hoàn toàn chính xác, trong đó điều này không khắc phục được vấn đề của tôi. Điều gì đặt ra câu hỏi ... _Tại sao điều này khắc phục được sự cố? Tôi mong đợi một tham chiếu đến một mảng tạm thời để vẫn là một tham chiếu, chỉ là một tham chiếu đến một cái gì đó mà chỉ được tổ chức trong bộ nhớ vì tham chiếu chính nó. Tôi cho rằng tôi nên đọc về quản lý bộ nhớ PHP, nếu tôi có thể tìm thấy một tài liệu như vậy. –

+0

Có, đặt & $ vals trong hàm setArray – malletjo

Trả lời

4

Vượt qua mảng như một tài liệu tham khảo:

function setArray(&$vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 

Tôi đoán (điều này có thể sai trong một số chi tiết) nhưng tại sao điều này làm cho mã của bạn hoạt động như mong đợi là khi bạn vượt qua giá trị, mọi thứ đều tuyệt vời cho cuộc gọi đến dumpArray() bên trong của setArray() vì tham chiếu đến mảng $vals bên trong setArray() vẫn tồn tại. Nhưng khi điều khiển trở về myfunc() thì biến tạm thời đó biến mất như tất cả các tham chiếu đến nó. Vì vậy, PHP dutifully thay đổi mảng để chuỗi giá trị thay vì tài liệu tham khảo trước khi deallocating bộ nhớ cho nó.Nhưng nếu bạn chuyển nó như là một tham chiếu từ myfunc() thì setArray() đang sử dụng tham chiếu đến mảng tồn tại khi kiểm soát trở về myfunc().

2

Thêm & vào chữ ký đối số đã sửa nó cho tôi. Điều này có nghĩa là hàm sẽ nhận địa chỉ bộ nhớ của mảng ban đầu.

function setArray(&$vals) { 
// ... 
} 

CodePad.

0

Tôi vừa gặp phải cùng một vấn đề này trong tình huống gần như giống hệt nhau (tôi đang viết một trình bao bọc PDO cho mục đích của riêng mình). Tôi nghi ngờ rằng PHP đã thay đổi tham chiếu thành giá trị khi không có biến nào khác tham chiếu cùng dữ liệu và Rick, nhận xét của bạn trong bản chỉnh sửa cho câu hỏi của bạn đã xác nhận rằng sự nghi ngờ, vì vậy cảm ơn bạn vì điều đó.

Bây giờ, với bất kỳ ai khác, tôi tin rằng tôi có giải pháp đơn giản nhất: chỉ cần đặt từng phần tử mảng có liên quan thành tham chiếu đến chính nó trước khi chuyển mảng tới call_user_func_array().

Tôi không chắc chính xác điều gì xảy ra trong nội bộ vì có vẻ như nó không hoạt động, nhưng phần tử trở thành tham chiếu một lần nữa (bạn có thể thấy với var_dump()) và call_user_func_array() rồi chuyển đối số tham chiếu như mong đợi. Sửa chữa này dường như hoạt động ngay cả khi phần tử mảng vẫn là một tham chiếu, vì vậy bạn không phải kiểm tra trước.

Trong mã của Rick, nó sẽ là một cái gì đó như thế này (tất cả mọi thứ sau khi tranh luận đầu tiên cho bind_param là bằng cách tham khảo, vì vậy tôi bỏ qua một đầu tiên và sửa chữa tất cả những người sau đó):

for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) { 
    $this->bindArgs[$i] = &$this->bindArgs[$i]; 
} 

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 
Các vấn đề liên quan