PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/Amfphp/Core/Amf/Serializer.php

https://bitbucket.org/trujka/goweb
PHP | 884 lines | 525 code | 76 blank | 283 comment | 78 complexity | fe7ba547a6c2bb956363287914c99b37 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. * This file is part of amfPHP
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the license that is bundled
  8. * with this package in the file license.txt.
  9. * @package Amfphp_Core_Amf
  10. */
  11. /**
  12. * AmfSerializer manages the job of translating PHP objects into
  13. * the actionscript equivalent via Amf. The main method of the serializer
  14. * is the serialize method which takes and AmfObject as it's argument
  15. * and builds the resulting Amf Message.
  16. * TODO spit into 2 classes, one for Amf0 , one for Amf3 or maybe more.
  17. *
  18. * @package Amfphp_Core_Amf
  19. */
  20. class Amfphp_Core_Amf_Serializer {
  21. /**
  22. *
  23. * @var String the output stream
  24. */
  25. private $outBuffer;
  26. /**
  27. *
  28. * @var Amfphp_Core_Amf_Packet
  29. */
  30. private $packet;
  31. /**
  32. * the maximum amount of objects stored for reference
  33. */
  34. const MAX_STORED_OBJECTS = 1024;
  35. /**
  36. *
  37. * used for Amf0 references
  38. * @var array
  39. */
  40. private $Amf0StoredObjects;
  41. /**
  42. *
  43. * used for Amf3 references
  44. * @var array
  45. */
  46. private $storedObjects;
  47. /**
  48. * amf3 references to strings
  49. * @var array
  50. */
  51. private $storedStrings;
  52. /**
  53. * used for traits references. key: class name. value: array(reference id, array(property names))
  54. * @var array
  55. */
  56. private $className2TraitsInfo;
  57. /**
  58. *
  59. * @param <Amfphp_Core_Amf_Packet> $packet
  60. */
  61. public function __construct(Amfphp_Core_Amf_Packet $packet) {
  62. $this->packet = $packet;
  63. $this->resetReferences();
  64. }
  65. /**
  66. * initialize reference arrays and counters. Call before writing a body or a header, as the indices are local to each message body or header
  67. */
  68. private function resetReferences(){
  69. $this->Amf0StoredObjects = array();
  70. $this->storedStrings = array();
  71. $this->storedObjects = array();
  72. $this->className2TraitsInfo = array();
  73. }
  74. /**
  75. * serializes the Packet passed in the constructor
  76. * TODO clean up the mess with the temp buffers. A.S.
  77. */
  78. public function serialize() {
  79. $this->writeInt(0); // write the version (always 0)
  80. $count = count($this->packet->headers);
  81. $this->writeInt($count); // write header count
  82. for ($i = 0; $i < $count; $i++) {
  83. $this->resetReferences();
  84. //write header
  85. $header = $this->packet->headers[$i];
  86. $this->writeUTF($header->name);
  87. if ($header->required) {
  88. $this->writeByte(1);
  89. } else {
  90. $this->writeByte(0);
  91. }
  92. $tempBuf = $this->outBuffer;
  93. $this->outBuffer = "";
  94. $this->writeData($header->data);
  95. $serializedHeader = $this->outBuffer;
  96. $this->outBuffer = $tempBuf;
  97. $this->writeLong(strlen($serializedHeader));
  98. $this->outBuffer .= $serializedHeader;
  99. }
  100. $count = count($this->packet->messages);
  101. $this->writeInt($count); // write the Message count
  102. for ($i = 0; $i < $count; $i++) {
  103. $this->resetReferences();
  104. //write body.
  105. $message = $this->packet->messages[$i];
  106. $this->currentMessage = & $message;
  107. $this->writeUTF($message->targetUri);
  108. $this->writeUTF($message->responseUri);
  109. //save the current buffer, and flush it to write the Message
  110. $tempBuf = $this->outBuffer;
  111. $this->outBuffer = "";
  112. $this->writeData($message->data);
  113. $serializedMessage = $this->outBuffer;
  114. $this->outBuffer = $tempBuf;
  115. $this->writeLong(strlen($serializedMessage));
  116. $this->outBuffer .= $serializedMessage;
  117. }
  118. return $this->outBuffer;
  119. }
  120. public function getOutput() {
  121. return $this->outBuffer;
  122. }
  123. /**
  124. * writeByte writes a singe byte to the output stream
  125. * 0-255 range
  126. *
  127. * @param int $b An int that can be converted to a byte
  128. */
  129. protected function writeByte($b) {
  130. $this->outBuffer .= pack("c", $b); // use pack with the c flag
  131. }
  132. /**
  133. * writeInt takes an int and writes it as 2 bytes to the output stream
  134. * 0-65535 range
  135. *
  136. * @param int $n An integer to convert to a 2 byte binary string
  137. */
  138. protected function writeInt($n) {
  139. $this->outBuffer .= pack("n", $n); // use pack with the n flag
  140. }
  141. /**
  142. * writeLong takes an int, float or double and converts it to a 4 byte binary string and
  143. * adds it to the output buffer
  144. *
  145. * @param long $l A long to convert to a 4 byte binary string
  146. */
  147. protected function writeLong($l) {
  148. $this->outBuffer .= pack("N", $l); // use pack with the N flag
  149. }
  150. /**
  151. * writeDouble takes a float as the input and writes it to the output stream.
  152. * Then if the system is big-endian, it reverses the bytes order because all
  153. * doubles passed via remoting are passed little-endian.
  154. *
  155. * @param double $d The double to add to the output buffer
  156. */
  157. protected function writeDouble($d) {
  158. $b = pack("d", $d); // pack the bytes
  159. if (Amfphp_Core_Amf_Util::isSystemBigEndian()) { // if we are a big-endian processor
  160. $r = strrev($b);
  161. } else { // add the bytes to the output
  162. $r = $b;
  163. }
  164. $this->outBuffer .= $r;
  165. }
  166. /**
  167. * writeUTF takes and input string, writes the length as an int and then
  168. * appends the string to the output buffer
  169. *
  170. * @param string $s The string less than 65535 characters to add to the stream
  171. */
  172. protected function writeUtf($s) {
  173. $this->writeInt(strlen($s)); // write the string length - max 65535
  174. $this->outBuffer .= $s; // write the string chars
  175. }
  176. /**
  177. * writeLongUTF will write a string longer than 65535 characters.
  178. * It works exactly as writeUTF does except uses a long for the length
  179. * flag.
  180. *
  181. * @param string $s A string to add to the byte stream
  182. */
  183. protected function writeLongUtf($s) {
  184. $this->writeLong(strlen($s));
  185. $this->outBuffer .= $s; // write the string chars
  186. }
  187. /**
  188. * writeBoolean writes the boolean code (0x01) and the data to the output stream
  189. *
  190. * @param bool $d The boolean value
  191. */
  192. protected function writeBoolean($d) {
  193. $this->writeByte(1); // write the "boolean-marker"
  194. $this->writeByte($d); // write the boolean byte (0 = FALSE; rest = TRUE)
  195. }
  196. /**
  197. * writeString writes the string code (0x02) and the UTF8 encoded
  198. * string to the output stream.
  199. * Note: strings are truncated to 64k max length. Use XML as type
  200. * to send longer strings
  201. *
  202. * @param string $d The string data
  203. */
  204. protected function writeString($d) {
  205. $count = strlen($d);
  206. if ($count < 65536) {
  207. $this->writeByte(2);
  208. $this->writeUTF($d);
  209. } else {
  210. $this->writeByte(12);
  211. $this->writeLongUTF($d);
  212. }
  213. }
  214. /**
  215. * writeXML writes the xml code (0x0F) and the XML string to the output stream
  216. * Note: strips whitespace
  217. * @param string $d The XML string
  218. */
  219. protected function writeXML(Amfphp_Core_Amf_Types_Xml $d) {
  220. if (!$this->handleReference($d->data, $this->Amf0StoredObjects)) {
  221. $this->writeByte(0x0F);
  222. $this->writeLongUTF(preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data)));
  223. }
  224. }
  225. /**
  226. * writeData writes the date code (0x0B) and the date value (milliseconds from 1 January 1970) to the output stream, along with an empty unsupported timezone
  227. *
  228. * @param Amfphp_Core_Amf_Types_Date $d The date value
  229. */
  230. protected function writeDate(Amfphp_Core_Amf_Types_Date $d) {
  231. $this->writeByte(0x0B);
  232. $this->writeDouble($d->timeStamp);
  233. $this->writeInt(0);
  234. }
  235. /**
  236. * writeNumber writes the number code (0x00) and the numeric data to the output stream
  237. * All numbers passed through remoting are floats.
  238. *
  239. * @param int $d The numeric data
  240. */
  241. protected function writeNumber($d) {
  242. $this->writeByte(0); // write the number code
  243. $this->writeDouble(floatval($d)); // write the number as a double
  244. }
  245. /**
  246. * writeNull writes the null code (0x05) to the output stream
  247. */
  248. protected function writeNull() {
  249. $this->writeByte(5); // null is only a 0x05 flag
  250. }
  251. /**
  252. * writeUndefined writes the Undefined code (0x06) to the output stream
  253. */
  254. protected function writeUndefined() {
  255. $this->writeByte(6); // Undefined is only a 0x06 flag
  256. }
  257. /**
  258. * writeObjectEnd writes the object end code (0x009) to the output stream
  259. */
  260. protected function writeObjectEnd() {
  261. $this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
  262. $this->writeByte(9);
  263. }
  264. /**
  265. * writeArrayOrObject first determines if the PHP array contains all numeric indexes
  266. * or a mix of keys. Then it either writes the array code (0x0A) or the
  267. * object code (0x03) and then the associated data.
  268. *
  269. * @param array $d The php array
  270. */
  271. protected function writeArrayOrObject($d) {
  272. if ($this->handleReference($d, $this->Amf0StoredObjects)) {
  273. return;
  274. }
  275. $numeric = array(); // holder to store the numeric keys
  276. $string = array(); // holder to store the string keys
  277. $len = count($d); // get the total number of entries for the array
  278. $largestKey = -1;
  279. foreach ($d as $key => $data) { // loop over each element
  280. if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
  281. $numeric[$key] = $data; // The key is an index in an array
  282. $largestKey = max($largestKey, $key);
  283. } else {
  284. $string[$key] = $data; // The key is a property of an object
  285. }
  286. }
  287. $num_count = count($numeric); // get the number of numeric keys
  288. $str_count = count($string); // get the number of string keys
  289. if (($num_count > 0 && $str_count > 0) ||
  290. ($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
  291. $this->writeByte(8); // write the mixed array code
  292. $this->writeLong($num_count); // write the count of items in the array
  293. $this->writeObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
  294. } else if ($num_count > 0) { // this is just an array
  295. $num_count = count($numeric); // get the new count
  296. $this->writeByte(10); // write the array code
  297. $this->writeLong($num_count); // write the count of items in the array
  298. for ($i = 0; $i < $num_count; $i++) { // write all of the array elements
  299. $this->writeData($numeric[$i]);
  300. }
  301. } else if ($str_count > 0) { // this is an object
  302. $this->writeByte(3); // this is an object so write the object code
  303. $this->writeObjectFromArray($string); // write the object name/value pairs
  304. } else { //Patch submitted by Jason Justman
  305. $this->writeByte(10); // make this an array still
  306. $this->writeInt(0); // give it 0 elements
  307. $this->writeInt(0); // give it an element pad, this looks like a bug in Flash,
  308. //but keeps the next alignment proper
  309. }
  310. }
  311. protected function writeReference($num) {
  312. $this->writeByte(0x07);
  313. $this->writeInt($num);
  314. }
  315. /**
  316. * Write a plain numeric array without anything fancy
  317. */
  318. protected function writePlainArray($d) {
  319. if (!$this->writeReferenceIfExists($d)) {
  320. $num_count = count($d);
  321. $this->writeByte(10); // write the mixed array code
  322. $this->writeLong($num_count); // write the count of items in the array
  323. for ($i = 0; $i < $num_count; $i++) { // write all of the array elements
  324. $this->writeData($d[$i]);
  325. }
  326. }
  327. }
  328. /**
  329. * writeObjectFromArray handles writing a php array with string or mixed keys. It does
  330. * not write the object code as that is handled by the writeArrayOrObject and this method
  331. * is shared with the CustomClass writer which doesn't use the object code.
  332. *
  333. * @param array $d The php array with string keys
  334. */
  335. protected function writeObjectFromArray($d) {
  336. foreach ($d as $key => $data) { // loop over each element
  337. $this->writeUTF($key); // write the name of the object
  338. $this->writeData($data); // write the value of the object
  339. }
  340. $this->writeObjectEnd();
  341. }
  342. /**
  343. * writeObject handles writing a php array with string or mixed keys. It does
  344. * not write the object code as that is handled by the writeArrayOrObject and this method
  345. * is shared with the CustomClass writer which doesn't use the object code.
  346. *
  347. * @param array $d The php array with string keys
  348. */
  349. protected function writeAnonymousObject($d) {
  350. if (!$this->handleReference($d, $this->Amf0StoredObjects)) {
  351. $this->writeByte(3);
  352. $objVars = (array) $d;
  353. foreach ($d as $key => $data) { // loop over each element
  354. if ($key[0] != "\0") {
  355. $this->writeUTF($key); // write the name of the object
  356. $this->writeData($data); // write the value of the object
  357. }
  358. }
  359. $this->writeObjectEnd();
  360. }
  361. }
  362. /**
  363. * writeTypedObject takes an instance of a class and writes the variables defined
  364. * in it to the output stream.
  365. * To accomplish this we just blanket grab all of the object vars with get_object_vars, minus the Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE field, whiuch is used as class name
  366. *
  367. * @param object $d The object to serialize the properties. The deserializer looks for Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE on this object and writes it as the class name.
  368. */
  369. protected function writeTypedObject($d) {
  370. if ($this->handleReference($d, $this->Amf0StoredObjects)) {
  371. return;
  372. }
  373. $this->writeByte(16); // write the custom class code
  374. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  375. $className = $d->$explicitTypeField;
  376. if (!$className) {
  377. throw new Amfphp_Core_Exception(Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE . " not found on a object that is to be sent as typed. " . print_r($d, true));
  378. }
  379. unset($d->$explicitTypeField);
  380. $this->writeUTF($className); // write the class name
  381. $objVars = $d;
  382. foreach ($objVars as $key => $data) { // loop over each element
  383. if ($key[0] != "\0") {
  384. $this->writeUTF($key); // write the name of the object
  385. $this->writeData($data); // write the value of the object
  386. }
  387. }
  388. $this->writeObjectEnd();
  389. }
  390. /**
  391. * writeData checks to see if the type was declared and then either
  392. * auto negotiates the type or relies on the user defined type to
  393. * serialize the data into Amf
  394. *
  395. * @param mixed $d The data
  396. */
  397. protected function writeData($d) {
  398. if ($this->packet->amfVersion == Amfphp_Core_Amf_Constants::AMF3_ENCODING) { //amf3 data. This is most often, so it's has been moved to the top to be first
  399. $this->writeByte(0x11);
  400. $this->writeAmf3Data($d);
  401. return;
  402. } elseif (is_int($d) || is_float($d)) { // double
  403. $this->writeNumber($d);
  404. return;
  405. } elseif (is_string($d)) { // string, long string
  406. $this->writeString($d);
  407. return;
  408. } elseif (is_bool($d)) { // boolean
  409. $this->writeBoolean($d);
  410. return;
  411. } elseif (is_null($d)) { // null
  412. $this->writeNull();
  413. return;
  414. } elseif (Amfphp_Core_Amf_Util::is_undefined($d)) { // undefined
  415. $this->writeUndefined();
  416. return;
  417. } elseif (is_array($d)) { // array
  418. $this->writeArrayOrObject($d);
  419. return;
  420. } elseif (Amfphp_Core_Amf_Util::is_date($d)) { // date
  421. $this->writeDate($d);
  422. return;
  423. } elseif (Amfphp_Core_Amf_Util::is_Xml ($d)) { // Xml (note, no XmlDoc in AMF0)
  424. $this->writeXML($d);
  425. return;
  426. } elseif (is_object($d)) {
  427. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  428. $hasExplicitType = isset($d->$explicitTypeField);
  429. $className = get_class($d);
  430. if ($hasExplicitType) {
  431. $this->writeTypedObject($d);
  432. return;
  433. }else{
  434. $this->writeAnonymousObject($d);
  435. return;
  436. }
  437. }
  438. throw new Amfphp_Core_Exception("couldn't write data " . print_r($d));
  439. }
  440. /* * ******************************************************************************
  441. * Amf3 related code
  442. * ***************************************************************************** */
  443. /**
  444. * @todo Is the reference still needed? PHP4 needed it for objects, but PHP5 always
  445. * passes objects by reference. And PHP5 uses a copy-on-write approach, so that all
  446. * values are passed as "reference", in case no changes take place.
  447. *
  448. * @todo no type markers ("\6", for example) in this method!
  449. */
  450. protected function writeAmf3Data(& $d) {
  451. if (is_int($d)) { //int
  452. $this->writeAmf3Number($d);
  453. return;
  454. } elseif (is_float($d)) { //double
  455. $this->outBuffer .= "\5";
  456. $this->writeDouble($d);
  457. return;
  458. } elseif (is_string($d)) { // string
  459. $this->outBuffer .= "\6";
  460. $this->writeAmf3String($d);
  461. return;
  462. } elseif (is_bool($d)) { // boolean
  463. $this->writeAmf3Bool($d);
  464. return;
  465. } elseif (is_null($d)) { // null
  466. $this->writeAmf3Null();
  467. return;
  468. } elseif (Amfphp_Core_Amf_Util::is_undefined($d)) { // undefined
  469. $this->writeAmf3Undefined();
  470. return;
  471. } elseif (Amfphp_Core_Amf_Util::is_date($d)) { // date
  472. $this->writeAmf3Date($d);
  473. return;
  474. } elseif (is_array($d)) { // array
  475. $this->writeAmf3Array($d);
  476. return;
  477. } elseif (Amfphp_Core_Amf_Util::is_byteArray($d)) { //byte array
  478. $this->writeAmf3ByteArray($d->data);
  479. return;
  480. } elseif (Amfphp_Core_Amf_Util::is_Xml ($d)) { // Xml
  481. $this->writeAmf3Xml($d);
  482. return;
  483. } elseif (Amfphp_Core_Amf_Util::is_XmlDocument ($d)) { // XmlDoc
  484. $this->writeAmf3XmlDocument($d);
  485. return;
  486. } elseif (is_object($d)) {
  487. $this->writeAmf3Object($d);
  488. return;
  489. }
  490. throw new Amfphp_Core_Exception("couldn't write object " . print_r($d, false));
  491. }
  492. /**
  493. * Write undefined (Amf3).
  494. *
  495. * @return nothing
  496. */
  497. protected function writeAmf3Undefined() {
  498. $this->outBuffer .= "\0";
  499. }
  500. /**
  501. * Write NULL (Amf3).
  502. *
  503. * @return nothing
  504. */
  505. protected function writeAmf3Null() {
  506. $this->outBuffer .= "\1";
  507. }
  508. /**
  509. * Write a boolean (Amf3).
  510. *
  511. * @param bool $d the boolean to serialise
  512. *
  513. * @return nothing
  514. */
  515. protected function writeAmf3Bool($d) {
  516. $this->outBuffer .= $d ? "\3" : "\2";
  517. }
  518. /**
  519. * Write an (un-)signed integer (Amf3).
  520. *
  521. * @see getAmf3Int()
  522. *
  523. * @param int $d the integer to serialise
  524. *
  525. * @return nothing
  526. */
  527. protected function writeAmf3Int($d) {
  528. $this->outBuffer .= $this->getAmf3Int($d);
  529. }
  530. /**
  531. * Write a string (Amf3). Strings are stored in a cache and in case the same string
  532. * is written again, a reference to the string is sent instead of the string itself.
  533. *
  534. * note: Sending strings larger than 268435455 (2^28-1 byte) will (silently) fail!
  535. *
  536. * note: The string marker is NOT sent here and has to be sent before, if needed.
  537. *
  538. *
  539. * @param string $d the string to send
  540. *
  541. * @return The reference index inside the lookup table is returned. In case of an empty
  542. * string which is sent in a special way, NULL is returned.
  543. */
  544. protected function writeAmf3String($d) {
  545. if ($d === '') {
  546. //Write 0x01 to specify the empty string ("UTF-8-empty")
  547. $this->outBuffer .= "\1";
  548. return;
  549. }
  550. if (!$this->handleReference($d, $this->storedStrings)) {
  551. $this->writeAmf3Int(strlen($d) << 1 | 1); // U29S-value
  552. $this->outBuffer .= $d;
  553. }
  554. }
  555. /**
  556. *@todo understand this bit about circular references! so can use handleReference like everywhere else
  557. * @param array $d
  558. * @param <type> $arrayCollectionable
  559. */
  560. protected function writeAmf3Array(array $d) {
  561. //Circular referencing is disabled in arrays
  562. //Because if the array contains only primitive values,
  563. //Then === will say that the two arrays are strictly equal
  564. //if they contain the same values, even if they are really distinct
  565. //if(($key = array_search($d, $this->storedObjects, TRUE)) === FALSE )
  566. //{
  567. if (count($this->storedObjects) < self::MAX_STORED_OBJECTS) {
  568. $this->storedObjects[] = & $d;
  569. }
  570. $numeric = array(); // holder to store the numeric keys >= 0
  571. $string = array(); // holder to store the string keys; actually, non-integer or integer < 0 are stored
  572. $len = count($d); // get the total number of entries for the array
  573. $largestKey = -1;
  574. foreach ($d as $key => $data) { // loop over each element
  575. if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
  576. $numeric[$key] = $data; // The key is an index in an array
  577. $largestKey = max($largestKey, $key);
  578. } else {
  579. $string[$key] = $data; // The key is a property of an object
  580. }
  581. }
  582. $num_count = count($numeric); // get the number of numeric keys
  583. $str_count = count($string); // get the number of string keys
  584. if (
  585. ($str_count > 0 && $num_count == 0) || // Only strings or negative integer keys are present.
  586. ($num_count > 0 && $largestKey != $num_count - 1) // Non-negative integer keys are present, but the array is not "dense" (it has gaps).
  587. ) { // this is a mixed array. Convert it to an anonymous object(to get get an Object type on the client)
  588. $anonymousObject = new stdClass();
  589. foreach($numeric as $key => $data){
  590. $anonymousObject->$key = $numeric[$key];
  591. }
  592. foreach($string as $key => $data){
  593. $anonymousObject->$key = $string[$key];
  594. }
  595. $this->writeAmf3Object($anonymousObject);
  596. } else { // this is just an array
  597. $num_count = count($numeric);
  598. $this->outBuffer .= "\11";
  599. $handle = $num_count * 2 + 1;
  600. $this->writeAmf3Int($handle);
  601. foreach ($string as $key => $val) {
  602. $this->writeAmf3String($key);
  603. $this->writeAmf3Data($val);
  604. }
  605. $this->writeAmf3String(""); //End start hash
  606. for ($i = 0; $i < $num_count; $i++) {
  607. $this->writeAmf3Data($numeric[$i]);
  608. }
  609. }
  610. }
  611. /**
  612. * Return the serialisation of the given integer (Amf3).
  613. *
  614. * note: There does not seem to be a way to distinguish between signed and unsigned integers.
  615. * This method just sends the lowest 29 bit as-is, and the receiver is responsible to interpret
  616. * the result as signed or unsigned based on some context.
  617. *
  618. * note: The limit imposed by Amf3 is 29 bit. So in case the given integer is longer than 29 bit,
  619. * only the lowest 29 bits will be serialised. No error will be logged!
  620. * @TODO refactor into writeAmf3Int
  621. *
  622. * @param int $d the integer to serialise
  623. *
  624. * @return string
  625. */
  626. protected function getAmf3Int($d) {
  627. /**
  628. * @todo The lowest 29 bits are kept and all upper bits are removed. In case of
  629. * an integer larger than 29 bits (32 bit, 64 bit, etc.) the value will effectively change! Maybe throw an exception!
  630. */
  631. $d &= 0x1fffffff;
  632. if ($d < 0x80) {
  633. return
  634. chr($d);
  635. } elseif ($d < 0x4000) {
  636. return
  637. chr($d >> 7 & 0x7f | 0x80) .
  638. chr($d & 0x7f);
  639. } elseif ($d < 0x200000) {
  640. return
  641. chr($d >> 14 & 0x7f | 0x80) .
  642. chr($d >> 7 & 0x7f | 0x80) .
  643. chr($d & 0x7f);
  644. } else {
  645. return
  646. chr($d >> 22 & 0x7f | 0x80) .
  647. chr($d >> 15 & 0x7f | 0x80) .
  648. chr($d >> 8 & 0x7f | 0x80) .
  649. chr($d & 0xff);
  650. }
  651. }
  652. protected function writeAmf3Number($d) {
  653. if (is_int($d) && $d >= -268435456 && $d <= 268435455) {//check valid range for 29bits
  654. $this->outBuffer .= "\4";
  655. $this->writeAmf3Int($d);
  656. } else {
  657. //overflow condition would occur upon int conversion
  658. $this->outBuffer .= "\5";
  659. $this->writeDouble($d);
  660. }
  661. }
  662. protected function writeAmf3Xml(Amfphp_Core_Amf_Types_Xml $d) {
  663. $d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data));
  664. $this->writeByte(0x0B);
  665. $this->writeAmf3String($d);
  666. }
  667. protected function writeAmf3XmlDocument(Amfphp_Core_Amf_Types_XmlDocument $d) {
  668. $d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data));
  669. $this->writeByte(0x07);
  670. $this->writeAmf3String($d);
  671. }
  672. protected function writeAmf3Date(Amfphp_Core_Amf_Types_Date $d) {
  673. $this->writeByte(0x08);
  674. $this->writeAmf3Int(1);
  675. $this->writeDouble($d->timeStamp);
  676. }
  677. protected function writeAmf3ByteArray($d) {
  678. $this->writeByte(0x0C);
  679. $this->writeAmf3ByteArrayMessage($d);
  680. }
  681. protected function writeAmf3ByteArrayMessage($d) {
  682. if (!$this->handleReference($d, $this->storedObjects)) {
  683. $obj_length = strlen($d);
  684. $this->writeAmf3Int($obj_length << 1 | 0x01);
  685. $this->outBuffer .= $d;
  686. }
  687. }
  688. /**
  689. * looks if $obj already has a reference. If it does, write it, and return true. If not, add it to the references array.
  690. * Depending on whether or not the spl_object_hash function can be used ( available (PHP >= 5.2), and can only be used on an object)
  691. * things are handled a bit differently:
  692. * - if possible, objects are hashed and the hash is used as a key to the references array. So the array has the structure hash => reference
  693. * - if not, the object is pushed to the references array, and array_search is used. So the array has the structure reference => object.
  694. * maxing out the number of stored references improves performance(tested with an array of 9000 identical objects). This may be because isset's performance
  695. * is linked to the size of the array. weird...
  696. *
  697. * This also means that 2 completely separate instances of a class but with the same values will be written fully twice if we can't use the hash system
  698. *
  699. * @param mixed $obj
  700. * @param array $references
  701. */
  702. private function handleReference(&$obj, array &$references){
  703. $key = false;
  704. if(is_object($obj) && function_exists("spl_object_hash")){
  705. $hash = spl_object_hash($obj);
  706. if(isset($references[$hash])){
  707. $key = $references[$hash];
  708. }else{
  709. if(count($references) >= self::MAX_STORED_OBJECTS){
  710. $references[$hash] = count($references);
  711. }
  712. }
  713. }else{
  714. //no hash available, use array with simple numeric keys
  715. $key = array_search($obj, $references, TRUE);
  716. //only store the object for future reference if it isn't already stored, and if there is space left
  717. $doStore = true;
  718. if($key !== false){
  719. $doStore = false;
  720. }else if(count($references) >= self::MAX_STORED_OBJECTS){
  721. $doStore = false;
  722. }
  723. if($doStore){
  724. $references[] = &$obj;
  725. }
  726. }
  727. if($key !== false){
  728. //reference exists. write it and return true
  729. if($this->packet->amfVersion == Amfphp_Core_Amf_Constants::AMF0_ENCODING){
  730. $this->writeReference($key);
  731. }else{
  732. $handle = $key << 1;
  733. $this->writeAmf3Int($handle);
  734. }
  735. return true;
  736. }else{
  737. return false;
  738. }
  739. }
  740. /**
  741. * writes an object.
  742. * @param object $d
  743. */
  744. protected function writeAmf3Object($d) {
  745. //Write the object tag
  746. $this->outBuffer .= "\12";
  747. if ($this->handleReference($d, $this->storedObjects)) {
  748. return;
  749. }
  750. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  751. if(isset ($d->$explicitTypeField)){
  752. //class name is set. send all properties as sealed members.
  753. $className = $d->$explicitTypeField;
  754. $propertyNames = null;
  755. if(isset ($this->className2TraitsInfo[$className])){
  756. //we have traits information and a reference for it, so use a traits reference
  757. $traitsInfo = $this->className2TraitsInfo[$className];
  758. $propertyNames = $traitsInfo["propertyNames"];
  759. $referenceId = $traitsInfo["referenceId"];
  760. $traitsReference = $referenceId << 2 | 1;
  761. $this->writeAmf3Int($traitsReference);
  762. }else{
  763. //no available traits information. Write the full object
  764. $propertyNames = array();
  765. foreach ($d as $key => $value) {
  766. if ($key[0] != "\0" && $key != $explicitTypeField) { //Don't show private members or explicit type
  767. $propertyNames[] = $key;
  768. }
  769. }
  770. //U29O-traits: 0011 in LSBs, and number of properties
  771. $numProperties = count($propertyNames);
  772. $traits = $numProperties << 4 | 3;
  773. $this->writeAmf3Int($traits);
  774. //class name
  775. $this->writeAmf3String($className);
  776. //list of property names
  777. foreach ($propertyNames as $propertyName){
  778. $this->writeAmf3String($propertyName);
  779. }
  780. //save for reference
  781. $traitsInfo = array("referenceId" => count($this->className2TraitsInfo), "propertyNames" => $propertyNames);
  782. $this->className2TraitsInfo[$className] = $traitsInfo;
  783. }
  784. //list of values
  785. foreach ($propertyNames as $propertyName){
  786. $this->writeAmf3Data($d->$propertyName);
  787. }
  788. }else{
  789. //anonymous object. So type this as a dynamic object with no sealed members.
  790. //U29O-traits : 1011.
  791. $this->writeAmf3Int(0xB);
  792. //no class name. empty string for anonymous object
  793. $this->writeAmf3String("");
  794. //name/value pairs for dynamic properties
  795. foreach ($d as $key => $value) {
  796. if ($key[0] != "\0" && $key != $explicitTypeField) { //Don't show private members or explicit type
  797. $this->writeAmf3String($key);
  798. $this->writeAmf3Data($value);
  799. }
  800. }
  801. //empty string, marks end of dynamic members
  802. $this->outBuffer .= "\1";
  803. }
  804. }
  805. }
  806. ?>