|
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 } |