web/lib/Zend/Amf/Parse/Amf3/Serializer.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Amf
       
    17  * @subpackage Parse_Amf3
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: Serializer.php 22101 2010-05-04 20:07:13Z matthew $
       
    21  */
       
    22 
       
    23 /** Zend_Amf_Constants */
       
    24 require_once 'Zend/Amf/Constants.php';
       
    25 
       
    26 
       
    27 /** Zend_Amf_Parse_Serializer */
       
    28 require_once 'Zend/Amf/Parse/Serializer.php';
       
    29 
       
    30 /** Zend_Amf_Parse_TypeLoader */
       
    31 require_once 'Zend/Amf/Parse/TypeLoader.php';
       
    32 
       
    33 /**
       
    34  * Detect PHP object type and convert it to a corresponding AMF3 object type
       
    35  *
       
    36  * @package    Zend_Amf
       
    37  * @subpackage Parse_Amf3
       
    38  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    39  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    40  */
       
    41 class Zend_Amf_Parse_Amf3_Serializer extends Zend_Amf_Parse_Serializer
       
    42 {
       
    43     /**
       
    44      * A constant empty string
       
    45      * @var string
       
    46      */
       
    47     protected $_strEmpty = '';
       
    48     
       
    49     /**
       
    50      * An array of reference objects per amf body
       
    51      * @var array
       
    52      */
       
    53     protected $_referenceObjects = array();
       
    54 
       
    55     /**
       
    56      * An array of reference strings per amf body
       
    57      * @var array
       
    58      */
       
    59     protected $_referenceStrings = array();
       
    60 
       
    61     /**
       
    62      * An array of reference class definitions, indexed by classname
       
    63      * @var array
       
    64      */
       
    65     protected $_referenceDefinitions = array();
       
    66 
       
    67     /**
       
    68      * Serialize PHP types to AMF3 and write to stream
       
    69      *
       
    70      * Checks to see if the type was declared and then either
       
    71      * auto negotiates the type or use the user defined markerType to
       
    72      * serialize the data from php back to AMF3
       
    73      *
       
    74      * @param  mixed $data
       
    75      * @param  int $markerType
       
    76      * @param  mixed $dataByVal
       
    77      * @return void
       
    78      */
       
    79     public function writeTypeMarker(&$data, $markerType = null, $dataByVal = false)
       
    80     {
       
    81         // Workaround for PHP5 with E_STRICT enabled complaining about "Only 
       
    82         // variables should be passed by reference"
       
    83         if ((null === $data) && ($dataByVal !== false)) {
       
    84             $data = &$dataByVal;
       
    85         }
       
    86         if (null !== $markerType) {
       
    87             // Write the Type Marker to denote the following action script data type
       
    88             $this->_stream->writeByte($markerType);
       
    89 
       
    90             switch ($markerType) {
       
    91                 case Zend_Amf_Constants::AMF3_NULL:
       
    92                     break;
       
    93                 case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE:
       
    94                     break;
       
    95                 case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE:
       
    96                     break;
       
    97                 case Zend_Amf_Constants::AMF3_INTEGER:
       
    98                     $this->writeInteger($data);
       
    99                     break;
       
   100                 case Zend_Amf_Constants::AMF3_NUMBER:
       
   101                     $this->_stream->writeDouble($data);
       
   102                     break;
       
   103                 case Zend_Amf_Constants::AMF3_STRING:
       
   104                     $this->writeString($data);
       
   105                     break;
       
   106                 case Zend_Amf_Constants::AMF3_DATE:
       
   107                     $this->writeDate($data);
       
   108                     break;
       
   109                 case Zend_Amf_Constants::AMF3_ARRAY:
       
   110                     $this->writeArray($data);
       
   111                     break;
       
   112                 case Zend_Amf_Constants::AMF3_OBJECT:
       
   113                     $this->writeObject($data);
       
   114                     break;
       
   115                 case Zend_Amf_Constants::AMF3_BYTEARRAY:
       
   116                     $this->writeByteArray($data);
       
   117                     break;
       
   118                 case Zend_Amf_Constants::AMF3_XMLSTRING;
       
   119                     $this->writeXml($data);
       
   120                     break;
       
   121                 default:
       
   122                     require_once 'Zend/Amf/Exception.php';
       
   123                     throw new Zend_Amf_Exception('Unknown Type Marker: ' . $markerType);
       
   124             }
       
   125         } else {
       
   126             // Detect Type Marker
       
   127             if (is_resource($data)) {
       
   128                 $data = Zend_Amf_Parse_TypeLoader::handleResource($data);
       
   129             }
       
   130             switch (true) {
       
   131                 case (null === $data):
       
   132                     $markerType = Zend_Amf_Constants::AMF3_NULL;
       
   133                     break;
       
   134                 case (is_bool($data)):
       
   135                     if ($data){
       
   136                         $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_TRUE;
       
   137                     } else {
       
   138                         $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_FALSE;
       
   139                     }
       
   140                     break;
       
   141                 case (is_int($data)):
       
   142                     if (($data > 0xFFFFFFF) || ($data < -268435456)) {
       
   143                         $markerType = Zend_Amf_Constants::AMF3_NUMBER;
       
   144                     } else {
       
   145                         $markerType = Zend_Amf_Constants::AMF3_INTEGER;
       
   146                     }
       
   147                     break;
       
   148                 case (is_float($data)):
       
   149                     $markerType = Zend_Amf_Constants::AMF3_NUMBER;
       
   150                     break;
       
   151                 case (is_string($data)):
       
   152                     $markerType = Zend_Amf_Constants::AMF3_STRING;
       
   153                     break;
       
   154                 case (is_array($data)):
       
   155                     $markerType = Zend_Amf_Constants::AMF3_ARRAY;
       
   156                     break;
       
   157                 case (is_object($data)):
       
   158                     // Handle object types.
       
   159                     if (($data instanceof DateTime) || ($data instanceof Zend_Date)) {
       
   160                         $markerType = Zend_Amf_Constants::AMF3_DATE;
       
   161                     } else if ($data instanceof Zend_Amf_Value_ByteArray) {
       
   162                         $markerType = Zend_Amf_Constants::AMF3_BYTEARRAY;
       
   163                     } else if (($data instanceof DOMDocument) || ($data instanceof SimpleXMLElement)) {
       
   164                         $markerType = Zend_Amf_Constants::AMF3_XMLSTRING;
       
   165                     } else {
       
   166                         $markerType = Zend_Amf_Constants::AMF3_OBJECT;
       
   167                     }
       
   168                     break;
       
   169                 default:
       
   170                     require_once 'Zend/Amf/Exception.php';
       
   171                     throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data));
       
   172             }
       
   173             $this->writeTypeMarker($data, $markerType);
       
   174         }
       
   175     }
       
   176 
       
   177     /**
       
   178      * Write an AMF3 integer
       
   179      *
       
   180      * @param int|float $data
       
   181      * @return Zend_Amf_Parse_Amf3_Serializer
       
   182      */
       
   183     public function writeInteger($int)
       
   184     {
       
   185         if (($int & 0xffffff80) == 0) {
       
   186             $this->_stream->writeByte($int & 0x7f);
       
   187             return $this;
       
   188         }
       
   189 
       
   190         if (($int & 0xffffc000) == 0 ) {
       
   191             $this->_stream->writeByte(($int >> 7 ) | 0x80);
       
   192             $this->_stream->writeByte($int & 0x7f);
       
   193             return $this;
       
   194         }
       
   195 
       
   196         if (($int & 0xffe00000) == 0) {
       
   197             $this->_stream->writeByte(($int >> 14 ) | 0x80);
       
   198             $this->_stream->writeByte(($int >> 7 ) | 0x80);
       
   199             $this->_stream->writeByte($int & 0x7f);
       
   200             return $this;
       
   201         }
       
   202 
       
   203         $this->_stream->writeByte(($int >> 22 ) | 0x80);
       
   204         $this->_stream->writeByte(($int >> 15 ) | 0x80);
       
   205         $this->_stream->writeByte(($int >> 8 ) | 0x80);
       
   206         $this->_stream->writeByte($int & 0xff);
       
   207         return $this;
       
   208     }
       
   209 
       
   210     /**
       
   211      * Send string to output stream, without trying to reference it.
       
   212      * The string is prepended with strlen($string) << 1 | 0x01
       
   213      *
       
   214      * @param  string $string
       
   215      * @return Zend_Amf_Parse_Amf3_Serializer
       
   216      */
       
   217     protected function writeBinaryString(&$string){
       
   218         $ref = strlen($string) << 1 | 0x01;
       
   219         $this->writeInteger($ref);
       
   220         $this->_stream->writeBytes($string);
       
   221 
       
   222         return $this;
       
   223     }
       
   224 
       
   225     /**
       
   226      * Send string to output stream
       
   227      *
       
   228      * @param  string $string
       
   229      * @return Zend_Amf_Parse_Amf3_Serializer
       
   230      */
       
   231     public function writeString(&$string)
       
   232     {
       
   233         $len = strlen($string);
       
   234         if(!$len){
       
   235             $this->writeInteger(0x01);
       
   236             return $this;
       
   237         }
       
   238 
       
   239         $ref = array_search($string, $this->_referenceStrings, true);
       
   240         if($ref === false){
       
   241             $this->_referenceStrings[] = $string;
       
   242             $this->writeBinaryString($string);
       
   243         } else {
       
   244             $ref <<= 1;
       
   245             $this->writeInteger($ref);
       
   246         }
       
   247 
       
   248         return $this;
       
   249     }
       
   250 
       
   251     /**
       
   252      * Send ByteArray to output stream
       
   253      *
       
   254      * @param  string|Zend_Amf_Value_ByteArray  $data
       
   255      * @return Zend_Amf_Parse_Amf3_Serializer
       
   256      */
       
   257     public function writeByteArray(&$data)
       
   258     {
       
   259         if ($this->writeObjectReference($data)) {
       
   260             return $this;
       
   261         }
       
   262 
       
   263         if (is_string($data)) {
       
   264             //nothing to do
       
   265         } else if ($data instanceof Zend_Amf_Value_ByteArray) {
       
   266             $data = $data->getData();
       
   267         } else {
       
   268             require_once 'Zend/Amf/Exception.php';
       
   269             throw new Zend_Amf_Exception('Invalid ByteArray specified; must be a string or Zend_Amf_Value_ByteArray');
       
   270         }
       
   271 
       
   272         $this->writeBinaryString($data);
       
   273 
       
   274         return $this;
       
   275     }
       
   276 
       
   277     /**
       
   278      * Send xml to output stream
       
   279      *
       
   280      * @param  DOMDocument|SimpleXMLElement  $xml
       
   281      * @return Zend_Amf_Parse_Amf3_Serializer
       
   282      */
       
   283     public function writeXml($xml)
       
   284     {
       
   285         if ($this->writeObjectReference($xml)) {
       
   286             return $this;
       
   287         }
       
   288 
       
   289         if(is_string($xml)) {
       
   290             //nothing to do
       
   291         } else if ($xml instanceof DOMDocument) {
       
   292             $xml = $xml->saveXml();
       
   293         } else if ($xml instanceof SimpleXMLElement) {
       
   294             $xml = $xml->asXML();
       
   295         } else {
       
   296             require_once 'Zend/Amf/Exception.php';
       
   297             throw new Zend_Amf_Exception('Invalid xml specified; must be a DOMDocument or SimpleXMLElement');
       
   298         }
       
   299 
       
   300         $this->writeBinaryString($xml);
       
   301 
       
   302         return $this;
       
   303     }
       
   304 
       
   305     /**
       
   306      * Convert DateTime/Zend_Date to AMF date
       
   307      *
       
   308      * @param  DateTime|Zend_Date $date
       
   309      * @return Zend_Amf_Parse_Amf3_Serializer
       
   310      */
       
   311     public function writeDate($date)
       
   312     {
       
   313         if ($this->writeObjectReference($date)) {
       
   314             return $this;
       
   315         }
       
   316 
       
   317         if ($date instanceof DateTime) {
       
   318             $dateString = $date->format('U') * 1000;
       
   319         } elseif ($date instanceof Zend_Date) {
       
   320             $dateString = $date->toString('U') * 1000;
       
   321         } else {
       
   322             require_once 'Zend/Amf/Exception.php';
       
   323             throw new Zend_Amf_Exception('Invalid date specified; must be a string DateTime or Zend_Date object');
       
   324         }
       
   325 
       
   326         $this->writeInteger(0x01);
       
   327         // write time to stream minus milliseconds
       
   328         $this->_stream->writeDouble($dateString);
       
   329         return $this;
       
   330     }
       
   331 
       
   332     /**
       
   333      * Write a PHP array back to the amf output stream
       
   334      *
       
   335      * @param array $array
       
   336      * @return Zend_Amf_Parse_Amf3_Serializer
       
   337      */
       
   338     public function writeArray(&$array)
       
   339     {
       
   340         // arrays aren't reference here but still counted
       
   341         $this->_referenceObjects[] = $array;
       
   342 
       
   343         // have to seperate mixed from numberic keys.
       
   344         $numeric = array();
       
   345         $string  = array();
       
   346         foreach ($array as $key => &$value) {
       
   347             if (is_int($key)) {
       
   348                 $numeric[] = $value;
       
   349             } else {
       
   350                 $string[$key] = $value;
       
   351             }
       
   352         }
       
   353 
       
   354         // write the preamble id of the array
       
   355         $length = count($numeric);
       
   356         $id     = ($length << 1) | 0x01;
       
   357         $this->writeInteger($id);
       
   358 
       
   359         //Write the mixed type array to the output stream
       
   360         foreach($string as $key => &$value) {
       
   361             $this->writeString($key)
       
   362                  ->writeTypeMarker($value);
       
   363         }
       
   364         $this->writeString($this->_strEmpty);
       
   365 
       
   366         // Write the numeric array to ouput stream
       
   367         foreach($numeric as &$value) {
       
   368             $this->writeTypeMarker($value);
       
   369         }
       
   370         return $this;
       
   371     }
       
   372 
       
   373     /**
       
   374      * Check if the given object is in the reference table, write the reference if it exists,
       
   375      * otherwise add the object to the reference table
       
   376      *
       
   377      * @param mixed $object object reference to check for reference
       
   378      * @param mixed $objectByVal object to check for reference
       
   379      * @return Boolean true, if the reference was written, false otherwise
       
   380      */
       
   381     protected function writeObjectReference(&$object, $objectByVal = false)
       
   382     {
       
   383         // Workaround for PHP5 with E_STRICT enabled complaining about "Only 
       
   384         // variables should be passed by reference"
       
   385         if ((null === $object) && ($objectByVal !== false)) {
       
   386             $object = &$objectByVal;
       
   387         }
       
   388 
       
   389         $ref = array_search($object, $this->_referenceObjects,true);
       
   390 
       
   391         // quickly handle object references
       
   392         if ($ref !== false){
       
   393             $ref <<= 1;
       
   394             $this->writeInteger($ref);
       
   395             return true;
       
   396         }
       
   397         $this->_referenceObjects[] = $object;
       
   398         return false;
       
   399     }
       
   400 
       
   401     /**
       
   402      * Write object to ouput stream
       
   403      *
       
   404      * @param  mixed $data
       
   405      * @return Zend_Amf_Parse_Amf3_Serializer
       
   406      */
       
   407     public function writeObject($object)
       
   408     {
       
   409         if($this->writeObjectReference($object)){
       
   410             return $this;
       
   411         }
       
   412 
       
   413         $className = '';
       
   414 
       
   415         //Check to see if the object is a typed object and we need to change
       
   416         switch (true) {
       
   417              // the return class mapped name back to actionscript class name.
       
   418             case ($className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object))):
       
   419                 break;
       
   420 
       
   421             // Check to see if the user has defined an explicit Action Script type.
       
   422             case isset($object->_explicitType):
       
   423                 $className = $object->_explicitType;
       
   424                 break;
       
   425 
       
   426             // Check if user has defined a method for accessing the Action Script type
       
   427             case method_exists($object, 'getASClassName'):
       
   428                 $className = $object->getASClassName();
       
   429                 break;
       
   430 
       
   431             // No return class name is set make it a generic object
       
   432             case ($object instanceof stdClass):
       
   433                 $className = '';
       
   434                 break;
       
   435 
       
   436              // By default, use object's class name
       
   437             default:
       
   438                 $className = get_class($object);
       
   439                 break;
       
   440         }
       
   441 
       
   442         $writeTraits = true;
       
   443 
       
   444         //check to see, if we have a corresponding definition
       
   445         if(array_key_exists($className, $this->_referenceDefinitions)){
       
   446             $traitsInfo    = $this->_referenceDefinitions[$className]['id'];
       
   447             $encoding      = $this->_referenceDefinitions[$className]['encoding'];
       
   448             $propertyNames = $this->_referenceDefinitions[$className]['propertyNames'];
       
   449 
       
   450             $traitsInfo = ($traitsInfo << 2) | 0x01;
       
   451 
       
   452             $writeTraits = false;
       
   453         } else {
       
   454             $propertyNames = array();
       
   455 
       
   456             if($className == ''){
       
   457                 //if there is no className, we interpret the class as dynamic without any sealed members
       
   458                 $encoding = Zend_Amf_Constants::ET_DYNAMIC;
       
   459             } else {
       
   460                 $encoding = Zend_Amf_Constants::ET_PROPLIST;
       
   461 
       
   462                 foreach($object as $key => $value) {
       
   463                     if( $key[0] != "_") {
       
   464                         $propertyNames[] = $key;
       
   465                     }
       
   466                 }
       
   467             }
       
   468 
       
   469             $this->_referenceDefinitions[$className] = array(
       
   470                         'id'            => count($this->_referenceDefinitions),
       
   471                         'encoding'      => $encoding,
       
   472                         'propertyNames' => $propertyNames,
       
   473                     );
       
   474 
       
   475             $traitsInfo = Zend_Amf_Constants::AMF3_OBJECT_ENCODING;
       
   476             $traitsInfo |= $encoding << 2;
       
   477             $traitsInfo |= (count($propertyNames) << 4);
       
   478         }
       
   479 
       
   480         $this->writeInteger($traitsInfo);
       
   481 
       
   482         if($writeTraits){
       
   483             $this->writeString($className);
       
   484             foreach ($propertyNames as $value) {
       
   485                 $this->writeString($value);
       
   486             }
       
   487         }
       
   488 
       
   489         try {
       
   490             switch($encoding) {
       
   491                 case Zend_Amf_Constants::ET_PROPLIST:
       
   492                     //Write the sealed values to the output stream.
       
   493                     foreach ($propertyNames as $key) {
       
   494                         $this->writeTypeMarker($object->$key);
       
   495                     }
       
   496                     break;
       
   497                 case Zend_Amf_Constants::ET_DYNAMIC:
       
   498                     //Write the sealed values to the output stream.
       
   499                     foreach ($propertyNames as $key) {
       
   500                         $this->writeTypeMarker($object->$key);
       
   501                     }
       
   502 
       
   503                     //Write remaining properties
       
   504                     foreach($object as $key => $value){
       
   505                         if(!in_array($key,$propertyNames) && $key[0] != "_"){
       
   506                             $this->writeString($key);
       
   507                             $this->writeTypeMarker($value);
       
   508                         }
       
   509                     }
       
   510 
       
   511                     //Write an empty string to end the dynamic part
       
   512                     $this->writeString($this->_strEmpty);
       
   513                     break;
       
   514                 case Zend_Amf_Constants::ET_EXTERNAL:
       
   515                     require_once 'Zend/Amf/Exception.php';
       
   516                     throw new Zend_Amf_Exception('External Object Encoding not implemented');
       
   517                     break;
       
   518                 default:
       
   519                     require_once 'Zend/Amf/Exception.php';
       
   520                     throw new Zend_Amf_Exception('Unknown Object Encoding type: ' . $encoding);
       
   521             }
       
   522         } catch (Exception $e) {
       
   523             require_once 'Zend/Amf/Exception.php';
       
   524             throw new Zend_Amf_Exception('Unable to writeObject output: ' . $e->getMessage(), 0, $e);
       
   525         }
       
   526 
       
   527         return $this;
       
   528     }
       
   529 }