3

I have the following class that I have written. Seems to work OK with simple objects that have one level but does not work well when objects have multiple levels (multi-arrays) and the XML is all messed up. Can anyone help me improve this so it will work with any object?

class XMLGenerator
{ 
       function __construct($obj,$root, $element, $fullXML = true) {     

          $array = $this->object_2_array($obj);
          $this->output = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
          $this->output .= $this->make($array, $root, $element, $fullXML);

       }

        //convert objects into arrays
        function object_2_array($result){

            $array = array();

            foreach ($result as $key => $value){

                if (is_object($value)){
                    $array[$key]=XMLGenerator::object_2_array($value);
                } elseif (is_array($value)){
                    $array[$key]=XMLGenerator::object_2_array($value);
                } else {
                    $array[$key]=$value;
                }
            }
            return $array;
        }   

       //make XML
       function make($array, $root, $element, $fullXML) {
          if (is_numeric($root)) {
             $xml = "<{$element}>\n";
          } else {
              $xml = "<{$root}>\n";
          }

          foreach ($array as $key => $value) {
             if (is_array($value)) {
                 if ($element == 'options'){  //workaround for orders 3 level problem, need to rethink this - LT
                    $xml .= $this->make($value, $key, $key, $fullXML); 
                 } else {
                    $xml .= $this->make($value, $element, $key, $fullXML);  
                 }              
             } else {

                 //any fields with HTML need wrapping in CDATA
                 if (($key === 'Description')||( $key === 'productDescription' )){
                    $value = '<![CDATA['. $value .']]>';
                 //remove any chars XML doesnt like  
                 } else {
                    $value = htmlentities($value,ENT_QUOTES, 'UTF-8');
                    $value = functions::xml_entities($value);    
                 }
                 //decide on node name

                if (is_numeric($key)) {
                   $xml .= "<{$root}>{$value}</{$root}>\n";
                } else {
                   $xml .= "<{$key}>{$value}</{$key}>\n";
                }           
             }
          }

          if (is_numeric($root)) {
             $xml .= "</{$element}>\n";
          } else {
              $xml .= "</{$root}>\n";
          } 

          return $xml;
       } 


       //save XML to file
       function saveFile($file){
            //create DOM to ensure all XML is valid before writing to file
            $doc = new DOMDocument();
            $doc->loadXML($this->output);

            if ($doc->save("$file")){
                return TRUE;
            } else {
                return FALSE;
            }
       }

}

The below is a simple object that works well with the above class.

Products Object ( [db_connection:protected] => 3779074 [prod_id:protected] => 0 [shopkeeper:protected] => 0 [fields] => Array (

        [productDescription] => Test
        [productName] => Test
        [UPC] => 123
    )

)

The below does not work well at all.

Order Object ( [db_connection:protected] => msSqlConnect Object ( [con] => [dbName] => )

[skID:protected] => 89137
[orderID:protected] => 482325
[order] => Array
    (
        [id] => 482325
        [customer] => 491936
        [net] => 1565.98
        [vat] => 274.05
        [billing_address] => Address Object
            (
                [db_connection:protected] => msSqlConnect Object
                    (
                        [con] => 
                        [dbName] => 
                    )

                [custID:protected] => 491936
                [addID:protected] => 156928
                [fields] => Array
                    (
                        [id] => 156928
                        [surname] => test
                        [forename] => test
                        [add1] => 89 testRoad
                        [add2] => 
                        [city] => City
                        [country] => GB
                        [postcode] => POSTCODE
                    )

            )
        [items] => Array
            (
                [0] => Array
                    (
                        [id] => 716549
                        [headerID] => 482325
                        [productID] => 4084620
                        [net] => 22.99
                        [vat] => 4.0233
                        [qty] => 1
                        [options] => Array
                            (
                                [0] => Array
                                    (
                                        [id] => 
                                        [orderDetailsID] => 716549
                                        [optionid] => 763217
                                        [optionCost] => 100
                                        [optionVAT] => 17.5
                                    )

                                [1] => Array
                                    (
                                        [id] => 
                                        [orderDetailsID] => 716549
                                        [optionid] => 763241
                                        [optionCost] => 10
                                        [optionVAT] => 1.75
                                    )

                            )

                    )

                [1] => Array
                    (
                        [id] => 716551
                        [headerID] => 482325
                        [productID] => 3779074
                        [net] => 1400
                        [vat] => 245
                        [qty] => 1
                        [options] => 
                    )
            )
    ) )

Many thanks in advance for any help.

2
  • What does it mean it doesn't work? Commented Feb 15, 2012 at 16:13
  • SOAP is generally the best way to convert objects to xml. Usually with the intent to ship it off to a webservice but there's nothing stopping you from just using the rendered xml. Commented Feb 15, 2012 at 16:18

1 Answer 1

6

Multiple levels require a recursive kind of processing - as you don't know the number of levels upfront. While doing the recursion you also need to take care which XML Elements are opened and such.

What you do is that you serialize a PHP object into XML. You're not the first one who needs this, PHP ships with a XML serializer that follows the WDDX specification, for example with the wddx_serialize_value function:

$object = (object) array('hello' => (object) array('value' => 'world') );

echo wddx_serialize_value($object);

Which will give this XML (Demo):

<wddxPacket version='1.0'>
  <header/>
  <data>
    <struct>
      <var name='php_class_name'>
        <string>stdClass</string>
      </var>
      <var name='hello'>
        <struct>
          <var name='php_class_name'>
            <string>stdClass</string>
          </var>
          <var name='value'>
            <string>world</string>
          </var>
        </struct>
      </var>
    </struct>
  </data>
</wddxPacket>

If you need a different output, you need to write the serialization on your own. Within symfony2 (Symfony2 Serializer Component) and Pear (XML_Serializer) you find existing PHP code that does serialization with XML output.

Sign up to request clarification or add additional context in comments.

5 Comments

or use XSLT to change the wddxPacket XML
Interesting, never heard of WDDX. How is it different from SOAP? Soap: "specification for exchanging structured information in the implementation of Web Services in computer networks" - WDDX: "easily exchange data with one another over the Web.".
SOAP is more complex and with RPC whereas WDDX is about serialization only and I think it's older and not really that complex.
@MikeB: "WDDX and XML-RPC, both created in 1998, were the precursors to SOAP and Web services. SOAP borrows the envelope/header/body structure and the transport + interaction neutrality from WDDX and the HTTP and RPC bindings from XML-RPC." - en.wikipedia.org/wiki/WDDX
@hakre Very cool. SOAP can be cumbersome to work with at times so it's nice to know there's a less complex solution to serializing objects in xml :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.