2012-12-28 11 views
6

mi sono imbattuto in un comportamento di php molto strano (5.3.2 su ubuntu 10.04). un unset che dovrebbe verificarsi nell'ambito locale sta influenzando l'ambito della funzione del chiamante. il seguente frammento è una semplificazione del mio codice che mostra quello che posso solo supporre è un bug:php unset riferimento locale che interessa lo scope globale

<?php 
function should_not_alter($in) 
{ 
    $in_ref =& $in['level1']; 
    should_only_unset_locally($in); 
    return $in; 
} 
function should_only_unset_locally($in) 
{ 
    unset($in['level1']['level2_0']); 
} 
$data = array('level1' => array('level2_0' => 'first value', 'level2_1' => 'second value')); 
$data = should_not_alter($data); //test 1 
//should_only_unset_locally($data); //test 2 
print_r($data); 
?> 

se si esegue quanto sopra si vedrà che il valore 'first value' è stato disinserito dalla matrice $data nell'ambito globale . tuttavia se commentate test 1 ed eseguite test 2 ciò non accade.

posso solo presumere che php non mi piace fare riferimento a un elemento di un array. nel mio codice ho bisogno di modificare $in_ref - da qui il motivo per la linea $in_ref =& $in['level1']; nel codice sopra. Mi rendo conto che la rimozione di questa linea risolverebbe il problema di 'first value' non impostato nell'ambito globale, ma questa non è un'opzione.

qualcuno può confermare se questo è il comportamento previsto di php?

ho il sospetto che sia un bug, piuttosto che una funzionalità, perché questo comportamento è incoerente con il modo in cui php gestisce gli ambiti ei riferimenti con le variabili normali (non-array). per esempio, utilizzando una stringa piuttosto che una funzione di matrice should_only_unset_locally() ha alcun effetto sulla portata globale:

<?php 

function should_not_alter($in) 
{ 
    $in_ref =& $in; 
    should_only_unset_locally($in); 
    return $in; 
} 
function should_only_unset_locally($in) 
{ 
    unset($in); 
} 
$data = 'original'; 
$data = should_not_alter($data); //test 1 
//should_only_unset_locally($data); //test 2 
print_r($data); 

?> 

sia test1 test2 o uscita original come previsto. in realtà, anche se $data è un array ma $in_ref viene fatto riferimento all'intero array (ovvero $in_ref =& $in;), il comportamento del buggy scompare.

aggiornamento

i have submitted a bug report

+0

Vedo che lo sviluppatore è stato assegnato a chiuso. Non sembra che abbia interpretato correttamente il problema. Sono curioso di sapere se hai già scritto o pensato di scrivere una sorta di follow up? – Corbin

+0

sì, ho visto anche quello. ho visto il tuo commento nella storia. sembra che lo sviluppatore non abbia capito cosa stavo cercando di mostrare. forse in realtà non ha eseguito il mio codice poiché dice che commentare la linea non fa differenza, e chiaramente lo fa. – mulllhausen

risposta

0
$data = should_not_alter($data) 

Questa linea sovrascrive la matrice $data con il valore restituito should_not_alter, che è $in. Questo è un comportamento normale.

Inoltre, mentre si sta creando un riferimento $in_ref =& $in['level1']; ma non si sta facendo nulla con esso. Non avrà alcun effetto sull'uscita del programma.

Risposta breve:

eliminare la variabile di riferimento tramite unset($in_ref) prima di chiamare la funzione should_only_unset_locally().

Risposta lunga:

Quando viene creato un riferimento a un elemento array, l'elemento di matrice viene sostituito con un riferimento. Questo comportamento è strano ma non è un bug: è una funzionalità del linguaggio ed è di progettazione.

consideri il seguente programma PHP:

<?php 
$a = array(
    'key1' => 'value1', 
    'key2' => 'value2', 
); 
$r = &$a['key1']; 
$a['key1'] = 'value3'; 
var_dump($a['key1']); 
var_dump($r); 
var_dump($a['key1'] === $r); 

Output: 
string(6) "value3" 
string(6) "value3" 
bool(true) 

Assegnare un valore a $a['key1'] cambia il valore di $r quanto entrambi riferimento allo stesso valore.Al contrario l'aggiornamento $r aggiornerà l'elemento dell'array:

$r = 'value4'; 
var_dump($a['key1']); 
var_dump($r); 

Output: 
string(6) "value4" 
string(6) "value4" 

Il valore non vive in $r o $a['key'] - questi sono solo i riferimenti. È come se entrambi facessero riferimento a qualche valore spettrale e nascosto. Strano, eh?

Per la maggior parte dei casi d'uso questo è il comportamento desiderato e utile.

Ora applica questo al tuo programma. La seguente riga modifica locale $in matrice e sostituisce l'elemento 'level1' con riferimento:

$in_ref = &$in['level1']; 

$in_ref non è un riferimento $in['level1'] - invece entrambi riferimento allo stesso valore spettrale. Così, quando questa linea viene intorno:

unset($in['level1']['level2_0']); 

PHP vede $in['level1'] come un riferimento a un valore spettrale e rimuove l'elemento 'level2_0'. E poiché si tratta di un riferimento, la rimozione viene avvertita anche nell'ambito della funzione should_not_alter().

La soluzione al vostro problema particolare è quello di distruggere la variabile di riferimento che automagicamente ripristinare $in['level1'] di nuovo al comportamento normale:

function should_not_alter($in) { 
    $in_ref =& $in['level1']; 
    // Do some stuff with $in_ref 
    // After you're done with it delete the reference to restore $in['level1'] 
    unset($in_ref); 
    should_only_unset_locally($in); 
    return $in; 
} 
+0

'$ in_ref = & $ in ['level1'];' sicuramente ha un effetto. se lo commentate, il "primo valore" non viene annullato nell'ambito globale durante il test 1 o il test 2. – mulllhausen

+0

Avete ragione. Tuttavia, l'ambito globale viene effettuato solo quando viene assegnato il valore '$ data'. – leepowers

+2

avresti qualche fonte che confermerebbe che è davvero come progettato e questo non è un bug? – eis

1

Yup, si presenta come un insetto.

Come suggerisce il nome della funzione, should_not_alter non deve modificare l'array poiché è passato per valore. (Ovviamente non baso questo nome, ma non dovrebbe alterare nulla in base alla sua definizione.)

Il fatto che commentando $in_ref =& $in['level1']; lo lascia da solo $in sembra essere un'ulteriore prova che si tratta di un bug. Questa è una strana stranezza. Non ho idea di cosa potrebbe accadere internamente per causare quello.

Presenterei un bug report sul bug tracker di PHP. Per quello che vale, esiste ancora in 5.4.6.

+0

lo farà, grazie. – mulllhausen

+0

Questo non è un bug. È di progettazione, vedi la mia risposta rivista: http://stackoverflow.com/a/14064555/212700 – leepowers