2011-12-21 10 views
6

Per un po 'di tempo, ho utilizzato una funzione ricorsiva "tradizionale" per appiattire gli array multidimensionali comePass-by-reference che non funziona con parametri aggiuntivi per array_walk_recursive, a meno che non si tratti del riferimento pass-by deprecato per le chiamate

$baseArray = array(array('alpha'), 
        array('beta','gamma'), 
        array(), 
        array(array('delta','epsilon'), 
         array('zeta',array('eta', 
              'theta' 
              ), 
          ), 
         ), 
        array('iota'), 
       ); 

in un semplice array 1-d.

Ieri sera, ho pensato di dare un'occhiata all'utilizzo di array_walk_recursive() per vedere se potevo renderlo più efficiente e più pulito.

Il mio primo tentativo non ebbe molto successo:

function flattenArray($arrayValue, $arrayKey, &$flatArray) { 
    $flatArray[] = $arrayValue; 
} 


$flattenedArray = array(); 
array_walk_recursive($baseArray,'flattenArray',$flattenedArray); 

ho pensato che dovrebbe funzionare, ma tutto quello che ho ottenuto è stato una serie di errori:

Warning: Cannot use a scalar value as an array in C:\xampp\htdocs\arrayTest.php on line 16 

e un risultato di:

array(0) { } 

Tipo hinting nella mia funzione flattenArray() mi ha dato

Catchable fatal error: Argument 3 passed to flattenArray() must be an array, integer given in C:\xampp\htdocs\arrayTest.php on line 16 

Utilizzando una chiusura ha dato risultati identici

L'unico modo ho potuto farlo funzionare (senza ricorrere all'utilizzo di una globale o statico per la mia flattenedArray) è stato utilizzando la chiamata in tempo pass-by-di riferimento:

function flattenArray($arrayValue, $arrayKey, $flatArray) { 
    $flatArray[] = $arrayValue; 
} 


$flattenedArray = array(); 
array_walk_recursive($baseArray,'flattenArray',&$flattenedArray); 

che produce il risultato corretto

array(9) { [0]=> string(5) "alpha" [1]=> string(4) "beta" [2]=> string(5) "gamma" [3]=> string(5) "delta" [4]=> string(7) "epsilon" [5]=> string(4) "zeta" [6]=> string(3) "eta" [7]=> string(5) "theta" [8]=> string(4) "iota" } 

ma mi dà un avvertimento non-inaspettato

Deprecated: Call-time pass-by-reference has been deprecated in C:\xampp\htdocs\arrayTest.php on line 22 

So che PHP è un linguaggio stravagante, ma questo sembra un po 'estremo. La documentazione mostra chiaramente che il primo parametro di array_walk_recursive è pass-by-reference, ma sembra che gli argomenti aggiuntivi possano essere pass-by-reference al momento della chiamata. Strano!

versione di PHP 5.3.8 è

Eventuali suggerimenti su come posso utilizzare array_walk_recursive per appiattire il mio allineamento correttamente, senza ottenere errori deprecate (oltre a deposito di un bug report).

EDIT

P.S.

Sono consapevole che posso aggirare questo problema utilizzando una chiusura:

$flattenedArray = array(); 
array_walk_recursive($baseArray, function($arrayValue, $arrayKey) use(&$flattenedArray){ $flattenedArray[] = $arrayValue; }); 
var_dump($flattenedArray); 

ma come questo è necessario per una libreria che attualmente permette l'uso con PHP 5.2.0, non è una soluzione pratica di utilizzare un funzionalità che richiede una versione significativamente successiva di PHP

risposta

6

pensare in questo modo: Si passa $flatArray in array_walk_recursiveper valore. array_walk_recursive inoltrerà ulteriormente l'argomento tramite il riferimento alla funzione. Ma poiché è stato passato a array_walk_recursive in base al valore, il riferimento indicherà già un valore diverso.

Lo so, questo potrebbe sembrare uno strano limite, ma quando ci si pensa, è anche logico.

A proposito, penso che tu abbia accidentalmente trovato un altro problema con questo, sembra effettivamente un grave danneggiamento della memoria (guarda i terzi elementi della matrice stampata @http://codepad.viper-7.com/ZYNrNd). Lo esaminerò.

Una nota a parte, un modo semplice per appiattire utilizza un RecursiveArrayIterator:

$flattenedArray = array(); 
foreach (new RecursiveIteratorIterator(
      new RecursiveArrayIterator($baseArray), 
      RecursiveIteratorIterator::LEAVES_ONLY 
     ) as $value) { 
    $flattenedArray[] = $value; 
} 
+0

Posso vedere la logica, anche se non sono sicuro di essere d'accordo con esso ... l'argomento arrayValue viene passato alla funzione di callback per riferimento, consentendo di modificare i valori dell'array - cambio di funzione (& $ arrayValue, $ arrayKey) { \t $ arrayValue. = 'TEST'; } array_walk_recursive ($ baseArray, 'changer'); var_dump ($ baseArray); –

+0

Il problema di corruzione della memoria mi sembra potenzialmente serio anche se .... non ne ho visto alcuna prova fino a quando non lo hai evidenziato –

+0

Penso certamente che ci dovrebbe essere qualche nota formale che indica che la chiamata per riferimento non può essere usata con argomenti aggiuntivi nella funzione di callback come per call_user_func(), non solo qualsiasi commento che potrei aggiungere –

1

Non particolarmente utile in questa fase.

Leggendo la documentazione di PHP, ho trovato che call_user_func() ha una nota contro è descrizione degli argomenti di parametri:

Nota che i parametri per call_user_func() non sono passati per riferimento.

ma array_walk_recursive() non ha tale avviso.

Questo può essere puramente un problema di documentazione ... se avessi visto un avviso simile nella documentazione array_walk_recursive(), probabilmente non l'avrei provato (anche se i curiosi potrebbero aver provato a prescindere, solo per vedere cosa è accaduto).

Tuttavia, non capisco perché entrambi i casi non debbano accettare gli argomenti pass-by-reference nella definizione della funzione di callback. Questo sembra un po 'come un passo indietro ... una funzionalità linguistica che funziona (anche se si usa il riferimento pass-by-call) non funziona più, senza fare affidamento su quella caratteristica deprecata.

0

Passo con riferimento al momento della chiamata è deprecata:

http://uk3.php.net/manual/en/ini.core.php#ini.allow-call-time-pass-reference

Questo non si nota nella documentazione array_walk_recursive perché non è specifico per tale funzione.

Una cosa che si potrebbe fare è passare un nome di oggetto e metodo, piuttosto che un nome di funzione, come callback e mantenere lo stato dell'array appiattito come membro di quell'oggetto.

ad es.

class ArrayFlattener 
{ 
    //implementation and array state 
} 

$flattener = new ArrayFlattener(); 
array_walk_recursive($baseArray, array($flattener, 'flatten')); 

print_r($flattener->getFlattenedArray()); 
+1

Apprezzo __call-time__ pass-by-di riferimento è deprecato, ma stavo cercando una scala pass-by-di riferimento che non è deprecato, ma non sembra lavorare quando viene utilizzato per __additional__ argomenti in un funzione di callback. –

+0

Potrei dover usare un attributo di classe come metodo fallback .... questo verrà usato all'interno di una classe una volta che posso farlo funzionare ... ma dovrò assicurarmi di inizializzare l'attributo ogni volta che uso il flatten metodo, che è overhead aggiuntivo –

1

Dal momento che le soluzioni sono versione-specifici (in base alla documentazione, PHP 5.2 non dovrebbe lamentarsi chiamata in tempo pass-by-reference), un'opzione è creare diversi script contenenti ogni implementazione, quindi fare in modo che un altro script includa condizionalmente lo script che definisce la funzione di conversione (e qualsiasi altro codice specifico della versione) in base alla versione di PHP.

if (! defined('PHP_VERSION_ID')) { 
    $version = explode('.', PHP_VERSION); 
    define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2])); 
    if (PHP_VERSION_ID < 50207) { 
     define('PHP_MAJOR_VERSION', $version[0]); 
     define('PHP_MINOR_VERSION', $version[1]); 
     define('PHP_RELEASE_VERSION', $version[2]); 
    } 
} 
if (PHP_VERSION_ID < 50300) { 
    include_once('php-5.2-.php'); 
} else { 
    include_once('php-5.3+.php'); 
} 
+0

Dovrei vedere come funzionerebbe, non è una soluzione particolarmente pulita, ma potrebbe darmi alcuni dei vantaggi in termini di prestazioni Ho bisogno senza interrompere la funzionalità tra le versioni di PHP .... if (version_compare (PHP_VERSION, '5.3.0', '<')) {etc –

Problemi correlati