php \domdocument - Come salvare HTML di DOMDocument senza wrapper HTML?




close manual (21)

L'aggiunta del tag <meta> attiverà il comportamento di riparazione di DOMDocument . La parte buona è che non è necessario aggiungere quel tag. Se non vuoi usare una codifica di tua scelta, basta passarla come argomento costruttore.

http://php.net/manual/en/domdocument.construct.php

$doc = new DOMDocument('1.0', 'UTF-8');
$node = $doc->createElement('div', 'Hello World');
$doc->appendChild($node);
echo $doc->saveHTML();

Produzione

<div>Hello World</div>

Grazie a @Bart

Sono la funzione di seguito, sto faticando a generare il DOMDocument senza aggiungere i wrapper di tag XML, HTML, body e p prima dell'output del contenuto. La soluzione suggerita:

$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));

Funziona solo quando il contenuto non contiene elementi a livello di blocco al suo interno. Tuttavia, quando lo fa, come nell'esempio seguente con l'elemento h1, l'output risultante da saveXML viene troncato in ...

<p> Se ti piace </ p>

Ho indicato questo post come una possibile soluzione alternativa, ma non riesco a capire come implementarlo in questa soluzione (vedi i tentativi commentati qui sotto).

Eventuali suggerimenti?

function rseo_decorate_keyword($postarray) {
    global $post;
    $keyword = "Jasmine Tea"
    $content = "If you like <h1>jasmine tea</h1> you will really like it with Jasmine Tea flavors. This is the last ocurrence of the phrase jasmine tea within the content. If there are other instances of the keyword jasmine tea within the text what happens to jasmine tea."
    $d = new DOMDocument();
    @$d->loadHTML($content);
    $x = new DOMXpath($d);
    $count = $x->evaluate("count(//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and (ancestor::b or ancestor::strong)])");
    if ($count > 0) return $postarray;
    $nodes = $x->query("//text()[contains(translate(., 'ABCDEFGHJIKLMNOPQRSTUVWXYZ', 'abcdefghjiklmnopqrstuvwxyz'), '$keyword') and not(ancestor::h1) and not(ancestor::h2) and not(ancestor::h3) and not(ancestor::h4) and not(ancestor::h5) and not(ancestor::h6) and not(ancestor::b) and not(ancestor::strong)]");
    if ($nodes && $nodes->length) {
        $node = $nodes->item(0);
        // Split just before the keyword
        $keynode = $node->splitText(strpos($node->textContent, $keyword));
        // Split after the keyword
        $node->nextSibling->splitText(strlen($keyword));
        // Replace keyword with <b>keyword</b>
        $replacement = $d->createElement('strong', $keynode->textContent);
        $keynode->parentNode->replaceChild($replacement, $keynode);
    }
$postarray['post_content'] = $d->saveXML($d->getElementsByTagName('p')->item(0));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->item(1));
//  $postarray['post_content'] = $d->saveXML($d->getElementsByTagName('body')->childNodes);
return $postarray;
}

Nessuna delle altre soluzioni al momento della stesura di questo documento (giugno 2012) è stata in grado di soddisfare completamente le mie esigenze, quindi ne ho scritto una che gestisce i seguenti casi:

  • Accetta contenuti di testo semplice che non hanno tag né contenuti HTML.
  • Non aggiunge alcun tag (compresi i tag <doctype> , <xml> , <html> , <body> e <p> )
  • Lascia tutto avvolto in <p> da solo.
  • Lascia il testo vuoto da solo.

Quindi ecco una soluzione che risolve questi problemi:

class DOMDocumentWorkaround
{
    /**
     * Convert a string which may have HTML components into a DOMDocument instance.
     *
     * @param string $html - The HTML text to turn into a string.
     * @return \DOMDocument - A DOMDocument created from the given html.
     */
    public static function getDomDocumentFromHtml($html)
    {
        $domDocument = new DOMDocument();

        // Wrap the HTML in <div> tags because loadXML expects everything to be within some kind of tag.
        // LIBXML_NOERROR and LIBXML_NOWARNING mean this will fail silently and return an empty DOMDocument if it fails.
        $domDocument->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);

        return $domDocument;
    }

    /**
     * Convert a DOMDocument back into an HTML string, which is reasonably close to what we started with.
     *
     * @param \DOMDocument $domDocument
     * @return string - The resulting HTML string
     */
    public static function getHtmlFromDomDocument($domDocument)
    {
        // Convert the DOMDocument back to a string.
        $xml = $domDocument->saveXML();

        // Strip out the XML declaration, if one exists
        $xmlDeclaration = "<?xml version=\"1.0\"?>\n";
        if (substr($xml, 0, strlen($xmlDeclaration)) == $xmlDeclaration) {
            $xml = substr($xml, strlen($xmlDeclaration));
        }

        // If the original HTML was empty, loadXML collapses our <div></div> into <div/>. Remove it.
        if ($xml == "<div/>\n") {
            $xml = '';
        }
        else {
            // Remove the opening <div> tag we previously added, if it exists.
            $openDivTag = "<div>";
            if (substr($xml, 0, strlen($openDivTag)) == $openDivTag) {
                $xml = substr($xml, strlen($openDivTag));
            }

            // Remove the closing </div> tag we previously added, if it exists.
            $closeDivTag = "</div>\n";
            $closeChunk = substr($xml, -strlen($closeDivTag));
            if ($closeChunk == $closeDivTag) {
                $xml = substr($xml, 0, -strlen($closeDivTag));
            }
        }

        return $xml;
    }
}

Ho anche scritto alcuni test che sarebbero vissuti nella stessa classe:

public static function testHtmlToDomConversions($content)
{
    // test that converting the $content to a DOMDocument and back does not change the HTML
    if ($content !== self::getHtmlFromDomDocument(self::getDomDocumentFromHtml($content))) {
        echo "Failed\n";
    }
    else {
        echo "Succeeded\n";
    }
}

public static function testAll()
{
    self::testHtmlToDomConversions('<p>Here is some sample text</p>');
    self::testHtmlToDomConversions('<div>Lots of <div>nested <div>divs</div></div></div>');
    self::testHtmlToDomConversions('Normal Text');
    self::testHtmlToDomConversions(''); //empty
}

Puoi verificare che funzioni per te. DomDocumentWorkaround::testAll() restituisce questo:

    Succeeded
    Succeeded
    Succeeded
    Succeeded

il mio server ha php 5.3 e non può aggiornare così queste opzioni

LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD

non sono per me

Per risolvere questo dico alla funzione SaveXML per stampare l'elemento Body e quindi basta sostituire il "body" con "div"

ecco il mio codice, spero che aiuti qualcuno:

<? 
$html = "your html here";
$tabContentDomDoc = new DOMDocument();
$tabContentDomDoc->loadHTML('<?xml encoding="UTF-8">'.$html);
$tabContentDomDoc->encoding = 'UTF-8';
$tabContentDomDocBody = $tabContentDomDoc->getElementsByTagName('body')->item(0);
if(is_object($tabContentDomDocBody)){
    echo (str_replace("body","div",$tabContentDomDoc->saveXML($tabContentDomDocBody)));
}
?>

l'utf-8 è per il supporto ebraico.


Basta rimuovere i nodi direttamente dopo aver caricato il documento con loadHTML ():

# remove <!DOCTYPE 
$doc->removeChild($doc->doctype);           

# remove <html><body></body></html> 
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Proprio come gli altri membri, per la prima volta mi sono divertito con la semplicità e l'incredibile potenza della risposta di @Alessandro Vendruscolo. La possibilità di passare semplicemente alcune costanti contrassegnate al costruttore sembrava troppo bella per essere vera. Per me lo era. Ho le versioni corrette sia di LibXML che di PHP, comunque non importa quale ancora aggiungerebbe il tag HTML alla struttura del nodo dell'oggetto Document.

La mia soluzione ha funzionato in modo migliore rispetto all'utilizzo del ...

$html->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

Bandiere o ....

# remove <!DOCTYPE 
$doc->removeChild($doc->firstChild);            

# remove <html><body></body></html>
$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);

Rimozione dei nodi, che diventa disordinata senza un ordine strutturato nel DOM. Di nuovo i frammenti di codice non hanno modo di predeterminare la struttura del DOM.

Ho iniziato questo viaggio volendo un modo semplice per fare DOM traversal come JQuery lo fa o almeno in qualche modo che ha avuto un set di dati strutturati singolarmente collegati, doppiamente collegati o attraversati da nodi di albero. Non mi importava fino a quando ho potuto analizzare una stringa come fa l'HTML e ho anche la straordinaria potenza delle proprietà della classe entità del nodo da utilizzare lungo la strada.

Finora DOMDocument Object mi ha lasciato il desiderio ... Come con molti altri programmatori sembra ... So di aver visto molta frustrazione in questa domanda, quindi da quando FINALMENTE .... (dopo circa 30 ore di tentativi e fallimenti test di tipo) Ho trovato un modo per ottenere tutto. Spero che questo aiuti qualcuno ...

Prima di tutto, sono cinico di TUTTO ... lol ...

Avrei passato una vita prima di essere d'accordo con chiunque che una classe di terze parti sia comunque necessaria in questo caso d'uso. Ero molto e NON sono un fan dell'uso di qualsiasi struttura di classe di terze parti, tuttavia mi sono imbattuto in un grande parser. (Circa 30 volte in Google prima di arrendermi quindi non mi sento da solo se l'hai evitato perché sembrava non ufficiale in alcun modo ...)

Se stai usando frammenti di codice e hai bisogno del codice, pulito e non influenzato dal parser in alcun modo, senza l'uso di tag extra, usa simplePHPParser .

È incredibile e si comporta molto come JQuery. Non mi entusiasma spesso, ma questa classe utilizza molti buoni strumenti e non ho ancora avuto errori di analisi. Sono un grande fan di poter fare ciò che fa questa classe.

Puoi trovare i suoi file da scaricare simplePHPParser , le sue istruzioni di avvio here e la sua API here . Consiglio vivamente di usare questa classe con i suoi metodi semplici che possono fare un .find(".className") nello stesso modo in cui sarebbe usato un metodo di ricerca JQuery o anche metodi familiari come getElementByTagName() o getElementById() ...

Quando si salva un albero dei nodi in questa classe, non aggiunge nulla. Puoi semplicemente dire $doc->save(); e restituisce l'intero albero a una stringa senza problemi.

Ora utilizzerò questo parser per tutti i progetti, con larghezza di banda non limitata, in futuro.


Se la soluzione per le bandiere fornita da Alessandro Vendruscolo non funziona, puoi provare questo:

$dom = new DOMDocument();
$dom->loadHTML($content);

//do your stuff..

$finalHtml = '';
$bodyTag = $dom->documentElement->getElementsByTagName('body')->item(0);
foreach ($bodyTag->childNodes as $rootLevelTag) {
    $finalHtml .= $dom->saveHTML($rootLevelTag);
}
echo $finalHtml;

$bodyTag conterrà il codice HTML completo elaborato senza tutti quei $bodyTag HTML, ad eccezione del tag <body> , che è la radice del contenuto. Quindi puoi usare una regex o una funzione trim per rimuoverla dalla stringa finale (dopo saveHTML ) o, come nel caso precedente, iterare su tutti i suoi childen, salvandone il contenuto in una variabile temporanea $finalHtml e restituirla (cosa credo di essere più sicuro).


Va bene ho trovato una soluzione più elegante, ma è solo noioso:

$d = new DOMDocument();
@$d->loadHTML($yourcontent);
...
// do your manipulation, processing, etc of it blah blah blah
...
// then to save, do this
$x = new DOMXPath($d);
$everything = $x->query("body/*"); // retrieves all elements inside body tag
if ($everything->length > 0) { // check if it retrieved anything in there
      $output = '';
      foreach ($everything as $thing) {
           $output .= $d->saveXML($thing);
      }
      echo $output; // voila, no more annoying html wrappers or body tag
}

Bene, spero che questo non tralascia nulla e aiuti qualcuno?


usa DOMDocumentFragment

$html = 'what you want';
$doc = new DomDocument();
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html);
$doc->appendChild($fragment);
echo $doc->saveHTML();

Ho PHP 5.3 e le risposte qui non hanno funzionato per me.

$doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild); Ho sostituito tutti i documenti con solo il primo figlio, ho avuto molti paragrafi e solo il primo è stato salvato, ma la soluzione mi ha dato un buon punto di partenza per scrivere qualcosa senza regex Ho lasciato alcuni commenti e sono abbastanza sicuro che questo può essere migliorato ma se qualcuno ha lo stesso problema come me può essere un buon punto di partenza.

function extractDOMContent($doc){
    # remove <!DOCTYPE
    $doc->removeChild($doc->doctype);

    // lets get all children inside the body tag
    foreach ($doc->firstChild->firstChild->childNodes as $k => $v) {
        if($k !== 0){ // don't store the first element since that one will be used to replace the html tag
            $doc->appendChild( clone($v) ); // appending element to the root so we can remove the first element and still have all the others
        }
    }
    // replace the body tag with the first children
    $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
    return $doc;
}

Quindi potremmo usarlo in questo modo:

$doc = new DOMDocument();
$doc->encoding = 'UTF-8';
$doc->loadHTML('<p>Some html here</p><p>And more html</p><p>and some html</p>');
$doc = extractDOMContent($doc);

Nota che appendChild accetta un DOMNode quindi non abbiamo bisogno di creare nuovi elementi, possiamo semplicemente riutilizzare quelli esistenti che implementano DOMNode come DOMElement questo può essere importante per mantenere il codice "sano" quando si manipolano più documenti HTML / XML


È il 2017 e per questa domanda del 2011 non mi piacciono le risposte. Un sacco di regex, grandi classi, loadXML, ecc ...

Soluzione semplice che risolve i problemi noti:

$dom = new DOMDocument();
$dom->loadHTML( '<html><body>'.mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8').'</body></html>' , LIBXML_HTML_NODEFDTD);
$html = substr(trim($dom->saveHTML()),12,-14);

Facile, semplice, solido, veloce. Questo codice funzionerà per quanto riguarda i tag HTML e la codifica come:

$html = '<p>äöü</p><p>ß</p>';

Se qualcuno trova un errore, per favore dillo, lo userò io stesso.

Modifica , Altre opzioni valide che funzionano senza errori (molto simili a quelle già fornite):

@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$saved_dom = trim($dom->saveHTML());
$start_dom = stripos($saved_dom,'<body>')+6;
$html = substr($saved_dom,$start_dom,strripos($saved_dom,'</body>') - $start_dom );

Potresti aggiungere tu stesso il corpo per prevenire qualsiasi cosa strana sulla pelliccia.

Opzione Thirt:

 $mock = new DOMDocument;
 $body = $dom->getElementsByTagName('body')->item(0);
  foreach ($body->childNodes as $child){
     $mock->appendChild($mock->importNode($child, true));
  }
$html = trim($mock->saveHTML());

Mi sono imbattuto anche in questo problema.

Sfortunatamente, non mi sono sentito a mio agio nell'usare una delle soluzioni fornite in questo thread, quindi sono andato a controllarne uno che mi soddisfacesse.

Ecco cosa ho inventato e funziona senza problemi:

$domxpath = new \DOMXPath($domDocument);

/** @var \DOMNodeList $subset */
$subset = $domxpath->query('descendant-or-self::body/*');

$html = '';
foreach ($subset as $domElement) {
    /** @var $domElement \DOMElement */
    $html .= $domDocument->saveHTML($domElement);
}

In essenza funziona in modo simile alla maggior parte delle soluzioni fornite qui, ma invece di fare lavoro manuale usa il selettore xpath per selezionare tutti gli elementi all'interno del corpo e concatena il loro codice html.


Usa questa funzione

$layout = preg_replace('~<(?:!DOCTYPE|/?(?:html|head|body))[^>]*>\s*~i', '', $layout);

Mi sono imbattuto in questo argomento per trovare un modo per rimuovere il wrapper HTML. Utilizzo di LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD funziona alla grande, ma ho un problema con utf-8. Dopo molti sforzi ho trovato una soluzione. Lo mando qui sotto per chiunque abbia lo stesso problema.

Il problema causato da <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Il problema:

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->saveHTML();

Soluzione 1:

$dom->loadHTML(mb_convert_encoding($document, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    $dom->saveHTML($dom->documentElement));

Soluzione 2:

$dom->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
utf8_decode($dom->saveHTML($dom->documentElement));

Avevo anche questo requisito e mi è piaciuta la soluzione postata da Alex sopra. Ci sono un paio di problemi, però - se l'elemento <body> contiene più di un elemento figlio, il documento risultante conterrà solo il primo elemento figlio di <body> , non tutti. Inoltre, avevo bisogno dello stripping per gestire le cose in modo condizionale - solo quando avevi un documento con le intestazioni HTML. Quindi l'ho rifinito come segue. Invece di rimuovere <body> , l'ho trasformato in <div> e ho rimosso la dichiarazione XML e <html> .

function strip_html_headings($html_doc)
{
    if (is_null($html_doc))
    {
        // might be better to issue an exception, but we silently return
        return;
    }

    // remove <!DOCTYPE 
    if (!is_null($html_doc->firstChild) &&
        $html_doc->firstChild->nodeType == XML_DOCUMENT_TYPE_NODE)
    {
        $html_doc->removeChild($html_doc->firstChild);     
    }

    if (!is_null($html_doc->firstChild) &&
        strtolower($html_doc->firstChild->tagName) == 'html' &&
        !is_null($html_doc->firstChild->firstChild) &&
        strtolower($html_doc->firstChild->firstChild->tagName) == 'body')
    {
        // we have 'html/body' - replace both nodes with a single "div"        
        $div_node = $html_doc->createElement('div');

        // copy all the child nodes of 'body' to 'div'
        foreach ($html_doc->firstChild->firstChild->childNodes as $child)
        {
            // deep copies each child node, with attributes
            $child = $html_doc->importNode($child, true);
            // adds node to 'div''
            $div_node->appendChild($child);
        }

        // replace 'html/body' with 'div'
        $html_doc->removeChild($html_doc->firstChild);
        $html_doc->appendChild($div_node);
    }
}

Forse sono troppo tardi Ma forse qualcuno (come me) ha ancora questo problema.
Quindi, nessuno dei precedenti ha funzionato per me. Poiché $ dom-> loadHTML chiude anche i tag aperti, non solo aggiunge tag html e body.
Quindi aggiungere un elemento <div> non funziona per me, perché a volte mi piace 3-4 div non chiuso nel pezzo html.
La mia soluzione:

1.) Aggiungi un marcatore da tagliare, quindi carica il pezzo html

$html_piece = "[MARK]".$html_piece."[/MARK]";
$dom->loadHTML($html_piece);

2.) do whatever you want with the document
3.) save html

$new_html_piece = $dom->saveHTML();

4.) before you return it, remove < p >< /p > tags from marker, strangely it is only appear on [MARK] but not on [/MARK]...!?

$new_html_piece = preg_replace( "/<p[^>]*?>(\[MARK\]|\s)*?<\/p>/", "[MARK]" , $new_html_piece );

5.) remove everything before and after marker

$pattern_contents = '{\[MARK\](.*?)\[\/MARK\]}is';
if (preg_match($pattern_contents, $new_html_piece, $matches)) {
    $new_html_piece = $matches[1];
}

6.) return it

return $new_html_piece;

It would be a lot easier if LIBXML_HTML_NOIMPLIED worked for me. It schould, but it is not. PHP 5.4.17, libxml Version 2.7.8.
I find really strange, I use the HTML DOM parser and then, to fix this "thing" I have to use regex... The whole point was, not to use regex ;)


Un trucco saveHTML è usare loadXML e quindi saveHTML . I tag html e body vengono inseriti nella fase di load , non nella fase di save .

$dom = new DOMDocument;
$dom->loadXML('<p>My DOMDocument contents are here</p>');
echo $dom->saveHTML();

NB che questo è un po 'hacky e dovresti usare la risposta di Jonah se riesci a farlo funzionare.



For anyone using Drupal, there's a built in function to do this:

https://api.drupal.org/api/drupal/modules!filter!filter.module/function/filter_dom_serialize/7.x

Code for reference:

function filter_dom_serialize($dom_document) {
  $body_node = $dom_document->getElementsByTagName('body')->item(0);
  $body_content = '';

  if ($body_node !== NULL) {
    foreach ($body_node->getElementsByTagName('script') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node);
    }

    foreach ($body_node->getElementsByTagName('style') as $node) {
      filter_dom_serialize_escape_cdata_element($dom_document, $node, '/*', '*/');
    }

    foreach ($body_node->childNodes as $child_node) {
      $body_content .= $dom_document->saveXML($child_node);
    }
    return preg_replace('|<([^> ]*)/>|i', '<$1 />', $body_content);
  }
  else {
    return $body_content;
  }
}

Utilizzare invece saveXML() e passare documentElement come argomento.

$innerHTML = '';
foreach ($document->getElementsByTagName('p')->item(0)->childNodes as $child) {
    $innerHTML .= $document->saveXML($child);
}
echo $innerHTML;

http://php.net/domdocument.savexml


La risposta di Alex è corretta, ma potrebbe causare il seguente errore sui nodi vuoti:

L'argomento 1 passato a DOMNode :: removeChild () deve essere un'istanza di DOMNode

Ecco che arriva la mia piccola mod:

    $output = '';
    $doc = new DOMDocument();
    $doc->loadHTML($htmlString); //feed with html here

    if (isset($doc->firstChild)) {

        /* remove doctype */

        $doc->removeChild($doc->firstChild);

        /* remove html and body */

        if (isset($doc->firstChild->firstChild->firstChild)) {
            $doc->replaceChild($doc->firstChild->firstChild->firstChild, $doc->firstChild);
            $output = trim($doc->saveHTML());
        }
    }
    return $output;

Aggiungere il trim () è anche una buona idea per rimuovere gli spazi bianchi.


NOTA PER PHP 7

Per aggiornare questa risposta in quanto ha acquisito una certa popolarità: questa risposta non si applica più a partire da PHP 7. Come spiegato nella sezione " Modifiche all'indietro incompatibili ", in PHP 7 foreach funziona sulla copia dell'array, quindi qualsiasi modifica sullo stesso array non si riflettono sul ciclo foreach. Maggiori dettagli al link

Spiegazione (citazione da php.net ):

Il primo modulo esegue il loop sull'array dato da array_expression. Ad ogni iterazione, il valore dell'elemento corrente viene assegnato a $ value e il puntatore dell'array interno viene avanzato di uno (quindi nella prossima iterazione, si guarderà al prossimo elemento).

Quindi, nel tuo primo esempio hai solo un elemento nell'array, e quando il puntatore viene spostato, l'elemento successivo non esiste, quindi dopo aver aggiunto il nuovo elemento foreach finisce perché già "decide" che è l'ultimo elemento.

Nel secondo esempio, si inizia con due elementi e il ciclo foreach non si trova nell'ultimo elemento, quindi valuta l'array alla successiva iterazione e quindi si rende conto che c'è un nuovo elemento nell'array.

Credo che questa sia una conseguenza di ciascuna parte della spiegazione nella documentazione, il che probabilmente significa che foreachfa tutta la logica prima di chiamare il codice {}.

Test case

Se esegui questo:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

Otterrai questo risultato:

1 2 3 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Il che significa che ha accettato la modifica e l'ha esaminata perché è stata modificata "in tempo". Ma se lo fai:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

Otterrete:

1 2 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Il che significa che l'array è stato modificato, ma da quando lo abbiamo modificato quando foreachera già nell'ultimo elemento dell'array, "ha deciso" di non effettuare più il ciclo, e anche se abbiamo aggiunto un nuovo elemento, lo abbiamo aggiunto "troppo tardi" e non è stato collegato.

Una spiegazione dettagliata può essere letta in Come funziona effettivamente PHP foreach? che spiega gli interni di questo comportamento.







php serialization domdocument