Implement interface ArrayAccess in the Value class
[plcapi.git] / src / Value.php
index fd4a840..f0d331a 100644 (file)
@@ -4,7 +4,7 @@ namespace PhpXmlRpc;
 
 use PhpXmlRpc\Helper\Charset;
 
-class Value
+class Value implements \Countable, \IteratorAggregate, \ArrayAccess
 {
     public static $xmlrpcI4 = "i4";
     public static $xmlrpcInt = "int";
@@ -28,29 +28,30 @@ class Value
         "base64" => 1,
         "array" => 2,
         "struct" => 3,
-        "null" => 1
-        );
+        "null" => 1,
+    );
 
-    /// @todo: does these need to be public?
-    public $me=array();
-    public $mytype=0;
-    public $_php_class=null;
+    /// @todo: do these need to be public?
+    public $me = array();
+    public $mytype = 0;
+    public $_php_class = null;
 
     /**
-      * @param mixed $val
-      * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
-      */
-    function __construct($val=-1, $type='') {
-        /// @todo: optimization creep - do not call addXX, do it all inline.
-        /// downside: booleans will not be coerced anymore
-        if($val!==-1 || $type!='')
-        {
-            // optimization creep: inlined all work done by constructor
-            switch($type)
-            {
+     * Build an xmlrpc value.
+     * When no value or type is passed in, the value is left uninitialized, and the value can be added later
+     *
+     * @param mixed $val
+     * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
+     */
+    public function __construct($val = -1, $type = '')
+    {
+        // optimization creep - do not call addXX, do it all inline.
+        // downside: booleans will not be coerced anymore
+        if ($val !== -1 || $type != '') {
+            switch ($type) {
                 case '':
-                    $this->mytype=1;
-                    $this->me['string']=$val;
+                    $this->mytype = 1;
+                    $this->me['string'] = $val;
                     break;
                 case 'i4':
                 case 'int':
@@ -60,161 +61,137 @@ class Value
                 case 'dateTime.iso8601':
                 case 'base64':
                 case 'null':
-                    $this->mytype=1;
-                    $this->me[$type]=$val;
+                    $this->mytype = 1;
+                    $this->me[$type] = $val;
                     break;
                 case 'array':
-                    $this->mytype=2;
-                    $this->me['array']=$val;
+                    $this->mytype = 2;
+                    $this->me['array'] = $val;
                     break;
                 case 'struct':
-                    $this->mytype=3;
-                    $this->me['struct']=$val;
+                    $this->mytype = 3;
+                    $this->me['struct'] = $val;
                     break;
                 default:
-                    error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
-            }
-            /*if($type=='')
-            {
-                $type='string';
-            }
-            if(static::$xmlrpcTypes[$type]==1)
-            {
-                $this->addScalar($val,$type);
+                    error_log("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
             }
-            elseif(static::$xmlrpcTypes[$type]==2)
-            {
-                $this->addArray($val);
-            }
-            elseif(static::$xmlrpcTypes[$type]==3)
-            {
-                $this->addStruct($val);
-            }*/
         }
     }
 
     /**
-      * Add a single php value to an (unitialized) xmlrpcval
-      * @param mixed $val
-      * @param string $type
-      * @return int 1 or 0 on failure
-      */
-    function addScalar($val, $type='string')
+     * Add a single php value to an (uninitialized) xmlrpc value.
+     *
+     * @param mixed $val
+     * @param string $type
+     *
+     * @return int 1 or 0 on failure
+     */
+    public function addScalar($val, $type = 'string')
     {
-        $typeof = null;
-        if(isset(static::$xmlrpcTypes[$type])) {
-            $typeof = static::$xmlrpcTypes[$type];
+        $typeOf = null;
+        if (isset(static::$xmlrpcTypes[$type])) {
+            $typeOf = static::$xmlrpcTypes[$type];
         }
 
-        if($typeof!=1)
-        {
-            error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
+        if ($typeOf !== 1) {
+            error_log("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
             return 0;
         }
 
         // coerce booleans into correct values
         // NB: we should either do it for datetimes, integers and doubles, too,
         // or just plain remove this check, implemented on booleans only...
-        if($type==static::$xmlrpcBoolean)
-        {
-            if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
-            {
-                $val=true;
-            }
-            else
-            {
-                $val=false;
+        if ($type == static::$xmlrpcBoolean) {
+            if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) {
+                $val = true;
+            } else {
+                $val = false;
             }
         }
 
-        switch($this->mytype)
-        {
+        switch ($this->mytype) {
             case 1:
-                error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
+                error_log('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
                 return 0;
             case 3:
-                error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
+                error_log('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
                 return 0;
             case 2:
                 // we're adding a scalar value to an array here
-                //$ar=$this->me['array'];
-                //$ar[]=new Value($val, $type);
-                //$this->me['array']=$ar;
-                // Faster (?) avoid all the costly array-copy-by-val done here...
-                $this->me['array'][]=new Value($val, $type);
+                $this->me['array'][] = new Value($val, $type);
+
                 return 1;
             default:
                 // a scalar, so set the value and remember we're scalar
-                $this->me[$type]=$val;
-                $this->mytype=$typeof;
+                $this->me[$type] = $val;
+                $this->mytype = $typeOf;
+
                 return 1;
         }
     }
 
     /**
-      * Add an array of xmlrpcval objects to an xmlrpcval
-      * @param array $vals
-      * @return int 1 or 0 on failure
-      *
-      * @todo add some checking for $vals to be an array of xmlrpcvals?
-      */
-    public function addArray($vals)
+     * Add an array of xmlrpc values objects to an xmlrpc value.
+     *
+     * @param Value[] $values
+     *
+     * @return int 1 or 0 on failure
+     *
+     * @todo add some checking for $values to be an array of xmlrpc values?
+     */
+    public function addArray($values)
     {
-        if($this->mytype==0)
-        {
-            $this->mytype=static::$xmlrpcTypes['array'];
-            $this->me['array']=$vals;
+        if ($this->mytype == 0) {
+            $this->mytype = static::$xmlrpcTypes['array'];
+            $this->me['array'] = $values;
+
             return 1;
-        }
-        elseif($this->mytype==2)
-        {
+        } elseif ($this->mytype == 2) {
             // we're adding to an array here
-            $this->me['array'] = array_merge($this->me['array'], $vals);
+            $this->me['array'] = array_merge($this->me['array'], $values);
+
             return 1;
-        }
-        else
-        {
-            error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
+        } else {
+            error_log('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
             return 0;
         }
     }
 
     /**
-     * Add an array of named xmlrpcval objects to an xmlrpcval
-     * @param array $vals
+     * Add an array of named xmlrpc value objects to an xmlrpc value.
+     *
+     * @param Value[] $values
+     *
      * @return int 1 or 0 on failure
      *
-     * @todo add some checking for $vals to be an array?
+     * @todo add some checking for $values to be an array?
      */
-    public function addStruct($vals)
+    public function addStruct($values)
     {
-        if($this->mytype==0)
-        {
-            $this->mytype=static::$xmlrpcTypes['struct'];
-            $this->me['struct']=$vals;
+        if ($this->mytype == 0) {
+            $this->mytype = static::$xmlrpcTypes['struct'];
+            $this->me['struct'] = $values;
+
             return 1;
-        }
-        elseif($this->mytype==3)
-        {
+        } elseif ($this->mytype == 3) {
             // we're adding to a struct here
-            $this->me['struct'] = array_merge($this->me['struct'], $vals);
+            $this->me['struct'] = array_merge($this->me['struct'], $values);
+
             return 1;
-        }
-        else
-        {
-            error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
+        } else {
+            error_log('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
             return 0;
         }
     }
 
     /**
-     * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
+     * Returns a string containing "struct", "array", "scalar" or "undef" describing the base type of the value.
+     *
      * @return string
      */
     public function kindOf()
     {
-        switch($this->mytype)
-        {
+        switch ($this->mytype) {
             case 3:
                 return 'struct';
                 break;
@@ -229,33 +206,31 @@ class Value
         }
     }
 
-    private function serializedata($typ, $val, $charset_encoding='')
+    protected function serializedata($typ, $val, $charsetEncoding = '')
     {
-        $rs='';
+        $rs = '';
 
-        if(!isset(static::$xmlrpcTypes[$typ])) {
+        if (!isset(static::$xmlrpcTypes[$typ])) {
             return $rs;
         }
 
-        switch(static::$xmlrpcTypes[$typ])
-        {
+        switch (static::$xmlrpcTypes[$typ]) {
             case 1:
-                switch($typ)
-                {
+                switch ($typ) {
                     case static::$xmlrpcBase64:
-                        $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
+                        $rs .= "<${typ}>" . base64_encode($val) . "</${typ}>";
                         break;
                     case static::$xmlrpcBoolean:
-                        $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
+                        $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
                         break;
                     case static::$xmlrpcString:
                         // G. Giunta 2005/2/13: do NOT use htmlentities, since
                         // it will produce named html entities, which are invalid xml
-                        $rs.="<${typ}>" . Charset::instance()->encode_entities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charset_encoding). "</${typ}>";
+                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
                         break;
                     case static::$xmlrpcInt:
                     case static::$xmlrpcI4:
-                        $rs.="<${typ}>".(int)$val."</${typ}>";
+                        $rs .= "<${typ}>" . (int)$val . "</${typ}>";
                         break;
                     case static::$xmlrpcDouble:
                         // avoid using standard conversion of float to string because it is locale-dependent,
@@ -263,132 +238,117 @@ class Value
                         // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
                         // The code below tries its best at keeping max precision while avoiding exp notation,
                         // but there is of course no limit in the number of decimal places to be used...
-                        $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
+                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
                         break;
                     case static::$xmlrpcDateTime:
-                        if (is_string($val))
-                        {
-                            $rs.="<${typ}>${val}</${typ}>";
-                        }
-                        else if(is_a($val, 'DateTime'))
-                        {
-                            $rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
-                        }
-                        else if(is_int($val))
-                        {
-                            $rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
-                        }
-                        else
-                        {
+                        if (is_string($val)) {
+                            $rs .= "<${typ}>${val}</${typ}>";
+                        } elseif (is_a($val, 'DateTime')) {
+                            $rs .= "<${typ}>" . $val->format('Ymd\TH:i:s') . "</${typ}>";
+                        } elseif (is_int($val)) {
+                            $rs .= "<${typ}>" . strftime("%Y%m%dT%H:%M:%S", $val) . "</${typ}>";
+                        } else {
                             // not really a good idea here: but what shall we output anyway? left for backward compat...
-                            $rs.="<${typ}>${val}</${typ}>";
+                            $rs .= "<${typ}>${val}</${typ}>";
                         }
                         break;
                     case static::$xmlrpcNull:
-                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding)
-                        {
-                            $rs.="<ex:nil/>";
-                        }
-                        else
-                        {
-                            $rs.="<nil/>";
+                        if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
+                            $rs .= "<ex:nil/>";
+                        } else {
+                            $rs .= "<nil/>";
                         }
                         break;
                     default:
                         // no standard type value should arrive here, but provide a possibility
-                        // for xmlrpcvals of unknown type...
-                        $rs.="<${typ}>${val}</${typ}>";
+                        // for xmlrpc values of unknown type...
+                        $rs .= "<${typ}>${val}</${typ}>";
                 }
                 break;
             case 3:
                 // struct
-                if ($this->_php_class)
-                {
-                    $rs.='<struct php_class="' . $this->_php_class . "\">\n";
-                }
-                else
-                {
-                    $rs.="<struct>\n";
+                if ($this->_php_class) {
+                    $rs .= '<struct php_class="' . $this->_php_class . "\">\n";
+                } else {
+                    $rs .= "<struct>\n";
                 }
                 $charsetEncoder = Charset::instance();
-                foreach($val as $key2 => $val2)
-                {
-                    $rs.='<member><name>'.$charsetEncoder->encode_entities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charset_encoding)."</name>\n";
+                foreach ($val as $key2 => $val2) {
+                    $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
                     //$rs.=$this->serializeval($val2);
-                    $rs.=$val2->serialize($charset_encoding);
-                    $rs.="</member>\n";
+                    $rs .= $val2->serialize($charsetEncoding);
+                    $rs .= "</member>\n";
                 }
-                $rs.='</struct>';
+                $rs .= '</struct>';
                 break;
             case 2:
                 // array
-                $rs.="<array>\n<data>\n";
-                foreach($val as $element)
-                {
+                $rs .= "<array>\n<data>\n";
+                foreach ($val as $element) {
                     //$rs.=$this->serializeval($val[$i]);
-                    $rs.=$element->serialize($charset_encoding);
+                    $rs .= $element->serialize($charsetEncoding);
                 }
-                $rs.="</data>\n</array>";
+                $rs .= "</data>\n</array>";
                 break;
             default:
                 break;
         }
+
         return $rs;
     }
 
     /**
-     * Returns xml representation of the value. XML prologue not included
-     * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
+     * Returns xml representation of the value. XML prologue not included.
+     *
+     * @param string $charsetEncoding the charset to be used for serialization. if null, US-ASCII is assumed
+     *
      * @return string
      */
-    public function serialize($charset_encoding='')
+    public function serialize($charsetEncoding = '')
     {
-        // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
+        // add check? slower, but helps to avoid recursion in serializing broken xmlrpc values...
         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
         //{
-            reset($this->me);
-            list($typ, $val) = each($this->me);
-            return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
-        //}
-    }
+        reset($this->me);
+        list($typ, $val) = each($this->me);
 
-    // DEPRECATED
-    function serializeval($o)
-    {
-        // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
-        //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
-        //{
-            $ar=$o->me;
-            reset($ar);
-            list($typ, $val) = each($ar);
-            return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
+        return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
         //}
     }
 
     /**
      * Checks whether a struct member with a given name is present.
-     * Works only on xmlrpcvals of type struct.
-     * @param string $m the name of the struct member to be looked up
+     * Works only on xmlrpc values of type struct.
+     *
+     * @param string $key the name of the struct member to be looked up
+     *
      * @return boolean
+     *
+     * @deprecated use array access, e.g. isset($val[$key])
      */
-    public function structmemexists($m)
+    public function structmemexists($key)
     {
-        return array_key_exists($m, $this->me['struct']);
+        return array_key_exists($key, $this->me['struct']);
     }
 
     /**
-     * Returns the value of a given struct member (an xmlrpcval object in itself).
-     * Will raise a php warning if struct member of given name does not exist
-     * @param string $m the name of the struct member to be looked up
-     * @return xmlrpcval
+     * Returns the value of a given struct member (an xmlrpc value object in itself).
+     * Will raise a php warning if struct member of given name does not exist.
+     *
+     * @param string $key the name of the struct member to be looked up
+     *
+     * @return Value
+     *
+     * @deprecated use array access, e.g. $val[$key]
      */
-    public function structmem($m)
+    public function structmem($key)
     {
-        return $this->me['struct'][$m];
+        return $this->me['struct'][$key];
     }
 
     /**
-     * Reset internal pointer for xmlrpcvals of type struct.
+     * Reset internal pointer for xmlrpc values of type struct.
+     * @deprecated iterate directly over the object using foreach instead
      */
     public function structreset()
     {
@@ -396,94 +356,67 @@ class Value
     }
 
     /**
-     * Return next member element for xmlrpcvals of type struct.
-     * @return xmlrpcval
+     * Return next member element for xmlrpc values of type struct.
+     *
+     * @return Value
+     *
+     * @deprecated iterate directly over the object using foreach instead
      */
     public function structeach()
     {
         return each($this->me['struct']);
     }
 
-    // DEPRECATED! this code looks like it is very fragile and has not been fixed
-    // for a long long time. Shall we remove it for 2.0?
-    function getval()
-    {
-        // UNSTABLE
-        reset($this->me);
-        list($a,$b)=each($this->me);
-        // contributed by I Sofer, 2001-03-24
-        // add support for nested arrays to scalarval
-        // i've created a new method here, so as to
-        // preserve back compatibility
-
-        if(is_array($b))
-        {
-            @reset($b);
-            while(list($id,$cont) = @each($b))
-            {
-                $b[$id] = $cont->scalarval();
-            }
-        }
-
-        // add support for structures directly encoding php objects
-        if(is_object($b))
-        {
-            $t = get_object_vars($b);
-            @reset($t);
-            while(list($id,$cont) = @each($t))
-            {
-                $t[$id] = $cont->scalarval();
-            }
-            @reset($t);
-            while(list($id,$cont) = @each($t))
-            {
-                @$b->$id = $cont;
-            }
-        }
-        // end contrib
-        return $b;
-    }
-
     /**
-     * Returns the value of a scalar xmlrpcval
+     * Returns the value of a scalar xmlrpc value.
+     *
      * @return mixed
      */
     public function scalarval()
     {
         reset($this->me);
-        list(,$b)=each($this->me);
+        list(, $b) = each($this->me);
+
         return $b;
     }
 
     /**
-     * Returns the type of the xmlrpcval.
-     * For integers, 'int' is always returned in place of 'i4'
+     * Returns the type of the xmlrpc value.
+     * For integers, 'int' is always returned in place of 'i4'.
+     *
      * @return string
      */
     public function scalartyp()
     {
         reset($this->me);
-        list($a,)=each($this->me);
-        if($a==static::xmlrpcI4)
-        {
-            $a=static::xmlrpcInt;
+        list($a,) = each($this->me);
+        if ($a == static::$xmlrpcI4) {
+            $a = static::$xmlrpcInt;
         }
+
         return $a;
     }
 
     /**
-     * Returns the m-th member of an xmlrpcval of struct type
-     * @param integer $m the index of the value to be retrieved (zero based)
-     * @return xmlrpcval
+     * Returns the m-th member of an xmlrpc value of struct type.
+     *
+     * @param integer $key the index of the value to be retrieved (zero based)
+     *
+     * @return Value
+     *
+     * @deprecated use array access, e.g. $val[$key]
      */
-    public function arraymem($m)
+    public function arraymem($key)
     {
-        return $this->me['array'][$m];
+        return $this->me['array'][$key];
     }
 
     /**
-     * Returns the number of members in an xmlrpcval of array type
+     * Returns the number of members in an xmlrpc value of array type.
+     *
      * @return integer
+     *
+     * @deprecated use count() instead
      */
     public function arraysize()
     {
@@ -491,11 +424,143 @@ class Value
     }
 
     /**
-     * Returns the number of members in an xmlrpcval of struct type
+     * Returns the number of members in an xmlrpc value of struct type.
+     *
      * @return integer
+     *
+     * @deprecated use count() instead
      */
     public function structsize()
     {
         return count($this->me['struct']);
     }
-}
+
+    /**
+     * Returns the number of members in an xmlrpc value:
+     * - 0 for uninitialized values
+     * - 1 for scalars
+     * - the number of elements for structs and arrays
+     *
+     * @return integer
+     */
+    public function count()
+    {
+        switch ($this->mytype) {
+            case 3:
+                return count($this->me['struct']);
+            case 2:
+                return count($this->me['array']);
+            case 1:
+                return 1;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Implements the IteratorAggregate interface
+     *
+     * @return ArrayIterator
+     */
+    public function getIterator() {
+        switch ($this->mytype) {
+            case 3:
+                return new \ArrayIterator($this->me['struct']);
+            case 2:
+                return new \ArrayIterator($this->me['array']);
+            case 1:
+                return new \ArrayIterator($this->me);
+            default:
+                return new \ArrayIterator();
+        }
+        return new \ArrayIterator();
+    }
+
+
+    public function offsetSet($offset, $value) {
+
+        switch ($this->mytype) {
+            case 3:
+                if (!($value instanceof \PhpXmlRpc\Value)) {
+                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Struct');
+                }
+                if (is_null($offset)) {
+                    // disallow struct members with empty names
+                    throw new \Exception('It is not possible to add anonymous members to an XML-RPC Struct');
+                } else {
+                    $this->me['struct'][$offset] = $value;
+                }
+                return;
+            case 2:
+                if (!($value instanceof \PhpXmlRpc\Value)) {
+                    throw new \Exception('It is only possible to add Value objects to an XML-RPC Array');
+                }
+                if (is_null($offset)) {
+                    $this->me['array'][] = $value;
+                } else {
+                    // nb: we are not checking that $offset is above the existing array range...
+                    $this->me['array'][$offset] = $value;
+                }
+                return;
+            case 1:
+// todo: handle i4 vs int
+                reset($this->me);
+                list($type,) = each($this->me);
+                if ($type != $offset) {
+                    throw new \Exception('');
+                }
+                $this->me[$type] = $value;
+                return;
+            default:
+                // it would be nice to allow empty values to be be turned into non-empty ones this way, but we miss info to do so
+                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be set using array index");
+        }
+    }
+
+    public function offsetExists($offset) {
+        switch ($this->mytype) {
+            case 3:
+                return isset($this->me['struct'][$offset]);
+            case 2:
+                return isset($this->me['array'][$offset]);
+            case 1:
+// todo: handle i4 vs int
+                return $offset == $this->scalartyp();
+            default:
+                return false;
+        }
+    }
+
+    public function offsetUnset($offset) {
+        switch ($this->mytype) {
+            case 3:
+                unset($this->me['struct'][$offset]);
+                return;
+            case 2:
+                unset($this->me['array'][$offset]);
+                return;
+            case 1:
+                // can not remove value from a scalar
+                throw new \Exception("XML-RPC Value is of type 'scalar' and its value can not be unset using array index");
+            default:
+                throw new \Exception("XML-RPC Value is of type 'undef' and its value can not be unset using array index");
+        }
+    }
+
+    public function offsetGet($offset) {
+        switch ($this->mytype) {
+            case 3:
+                return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
+            case 2:
+                return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
+            case 1:
+// on bad type: null or exception?
+                reset($this->me);
+                list($type, $value) = each($this->me);
+                return $type == $offset ? $value : null;
+            default:
+// return null or exception?
+                throw new \Exception("XML-RPC Value is of type 'undef' and can not be accessed using array index");
+        }
+    }
+}
\ No newline at end of file