Search code examples
phpsimplexml

Copy SimpleXMLElement children to a different object


Given two SimpleXMLElement objects structured as follows (identical):

$obj1

object SimpleXMLElement {
    ["@attributes"] =>
    array(0) {
    }
    ["event"] =>
    array(1) {
        [0] =>
        object(SimpleXMLElement) {
            ["@attributes"] =>
            array(1) {
                ["url"] =>
                string "example.com"
            }
        }
    }
}

$obj2

object SimpleXMLElement {
    ["@attributes"] =>
    array(0) {
    }
    ["event"] =>
    array(1) {
        [0] =>
        object(SimpleXMLElement) {
            ["@attributes"] =>
            array(1) {
                ["url"] =>
                string "another-example.com"
            }
        }
    }
}

I am trying to copy all the child event items from the second object to the first as so:

foreach ($obj2->event as $event) {
    $obj1->event[] = $event
}

They move, but the copied objects are now empty.

$obj1

object SimpleXMLElement {
    ["@attributes"] =>
    array(0) {
    }
    ["event"] =>
    array(2) {
        [0] =>
        object(SimpleXMLElement) {
            ["@attributes"] =>
            array(1) {
                ["url"] =>
                string "example.com"
            }
        }
        [1] =>
        object(SimpleXMLElement) {
        }
    }
}

Solution

  • SimpleXML's editing functionality is rather limited, and in this case, both the assignment you're doing and ->addChild can only set the text content of the new element, not its attributes and children.

    This may be one case where you need the power of the more complex DOM API. Luckily, you can mix the two in PHP with almost no penalty, using dom_import_simplexml and simplexml_import_dom to switch to the other wrapper.

    Once you have a DOM representation, you can use the appendChild method of DOMNode to add a full node, but there's a catch - you can only add nodes "owned by" the same document. So you have to first call the importNode method on the document you're trying to edit.

    Putting it all together, you get something like this:

    // Set up the objects you're copying from and to
    // These don't need to be complete documents, they could be any element
    $obj1 = simplexml_load_string('<foo><event url="example.com" /></foo>');
    $obj2 = simplexml_load_string('<foo><event url="another.example.com" /></foo>');
    
    // Get a DOM representation of the root element we want to add things to
    // Note that $obj1_dom will always be a DOMElement not a DOMDocument, 
    //   because SimpleXML has no "document" object
    $obj1_dom = dom_import_simplexml($obj1);
    
    // Loop over the SimpleXML version of the source elements
    foreach ($obj2->event as $event) {
        // Get the DOM representation of the element we want to copy
        $event_dom = dom_import_simplexml($event);
        // Copy the element into the "owner document" of our target node
        $event_dom_adopted = $obj1_dom->ownerDocument->importNode($event_dom, true);
        // Add the node as a new child
        $obj1_dom->appendChild($event_dom_adopted);
    }
    
    // Check that we have the right output, not trusting var_dump or print_r
    // Note that we don't need to convert back to SimpleXML 
    // - $obj1 and $obj1_dom refer to the same data internally
    echo $obj1->asXML();