2015-04-22 6 views
5

Ho un oggetto che implementa Iterator e contiene 2 matrici: "voci" e "pagine". Ogni volta che eseguo il loop di questo oggetto, voglio modificare l'array di voci, ma ottengo l'errore An iterator cannot be used with foreach by reference che vedo avviato in PHP 5.2.PHP Un iteratore non può essere utilizzato con foreach per riferimento

La mia domanda è, come è possibile utilizzare la classe Iterator per modificare il valore dell'oggetto in loop mentre si utilizza foreach?

Il mio codice:

//$flavors = instance of this class: 
class PaginatedResultSet implements \Iterator { 
    private $position = 0; 

    public $entries = array(); 
    public $pages = array(); 

    //...Iterator methods... 
} 

//looping 
//throws error here 
foreach ($flavors as &$flavor) { 
    $flavor = $flavor->stdClassForApi(); 
} 

La ragione di questo è che a volte si $flavorsnon essere un un'istanza della mia classe e invece sarà solo una matrice semplice. Voglio essere in grado di modificare facilmente questo array indipendentemente dal tipo che è.

+0

perché non basta controllare se si tratta di una classe o r un array prima e poi trattarlo in modo appropriato? – MuppetGrinder

+0

@MuppetGrinder Perché uso questi metodi in molti posti e non voglio dover fare quel controllo ogni volta, invece solo in 1 posto. – shiznatix

risposta

5

Ho appena cercato di creare un iteratore che ha usato:

public function &current() { 
    $element = &$this->array[$this->position]; 
    return $element; 
} 

ma che ancora non ha funzionato.

Il meglio che posso consigliare è di implementare \ArrayAccess, che vi permetterà di fare questo:

foreach ($flavors as $key => $flavor) { 
    $flavors[$key] = $flavor->stdClassForApi(); 
} 

Uso generatori:

Aggiornamento sulla base di indicatori di commento su generatori, il seguente volontà consente di scorrere i risultati senza dover implementare \Iterator o \ArrayAccess.

class PaginatedResultSet { 
    public $entries = array(); 

    public function &iterate() 
    { 
     foreach ($this->entries as &$v) { 
      yield $v; 
     } 
    } 
} 

$flavors = new PaginatedResultSet(/* args */); 

foreach ($flavors->iterate() as &$flavor) { 
    $flavor = $flavor->stdClassForApi(); 
} 

Questa è una funzionalità disponibile in PHP 5.5.

+0

È anche possibile accedere agli elementi di un generatore "per riferimento", in modo che potrebbe essere un approccio alternativo –

+0

Ho finito per implementare sia \ ArrayAccess che \ Iterator e quindi utilizzare lo $ key => $ val style anziché per riferimento . Questo ha funzionato perfettamente. – shiznatix

+0

@MarkBaker Ho ancora bisogno di esaminare come funzionano, sono un po 'perplesso su cosa significano quando dicono che lo stato della funzione non cambia ad ogni chiamata. – Flosculus

0

Espansione sulla soluzione di Flosculus, se non si desidera fare riferimento alla chiave ogni volta che si utilizza la variabile iterata, è possibile assegnare un riferimento ad essa a una nuova variabile nella prima riga del foreach.

foreach ($flavors as $key => $f) { 
    $flavor = &$flavors[$key]; 
    $flavor = $flavor->stdClassForApi(); 
} 

Questo è funzionalmente identico ad usare la chiave per l'oggetto base, ma aiuta a mantenere il codice pulito, e nomi di variabili breve ... Se siete in questo genere di cose.

0

Se implementato le funzioni iteratore nel calss, vorrei suggerire di aggiungere un altro metodo per il "setCurrent()" class:

//$flavors = instance of this class: 
class PaginatedResultSet implements \Iterator { 
    private $position = 0; 

    public $entries = array(); 
    public $pages = array(); 

    /* --- Iterator methods block --- */ 
    private $current; 

    public function setCurrent($value){ 
     $this->current = $value; 
    } 

    public function current(){ 
     return $this->current; 
    } 
    //...Other Iterator methods... 
} 

allora si può solo utilizzare questa funzione all'interno del ciclo foreach:

foreach ($flavors as $flavor) { 
    $newFlavor = makeNewFlavorFromOldOne($flavor) 
    $flavors -> setCurrent($newFlavor); 
} 

Se avete bisogno di questa funzione in altre classi, è anche possibile definire un nuovo iteratore ed estendere l'interfaccia Iterator per contenere setCurrent()

Problemi correlati