2
0

Encoder.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  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_Json
  17. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * Encode PHP constructs to JSON
  22. *
  23. * @category Zend
  24. * @package Zend_Json
  25. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  26. * @license http://framework.zend.com/license/new-bsd New BSD License
  27. */
  28. class Zend_Json_Encoder
  29. {
  30. /**
  31. * Whether or not to check for possible cycling
  32. *
  33. * @var boolean
  34. */
  35. protected $_cycleCheck;
  36. /**
  37. * Additional options used during encoding
  38. *
  39. * @var array
  40. */
  41. protected $_options = array();
  42. /**
  43. * Array of visited objects; used to prevent cycling.
  44. *
  45. * @var array
  46. */
  47. protected $_visited = array();
  48. /**
  49. * Constructor
  50. *
  51. * @param boolean $cycleCheck Whether or not to check for recursion when encoding
  52. * @param array $options Additional options used during encoding
  53. * @return void
  54. */
  55. protected function __construct($cycleCheck = false, $options = array())
  56. {
  57. $this->_cycleCheck = $cycleCheck;
  58. $this->_options = $options;
  59. }
  60. /**
  61. * Use the JSON encoding scheme for the value specified
  62. *
  63. * @param mixed $value The value to be encoded
  64. * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
  65. * @param array $options Additional options used during encoding
  66. * @return string The encoded value
  67. */
  68. public static function encode($value, $cycleCheck = false, $options = array())
  69. {
  70. $encoder = new self(($cycleCheck) ? true : false, $options);
  71. return $encoder->_encodeValue($value);
  72. }
  73. /**
  74. * Recursive driver which determines the type of value to be encoded
  75. * and then dispatches to the appropriate method. $values are either
  76. * - objects (returns from {@link _encodeObject()})
  77. * - arrays (returns from {@link _encodeArray()})
  78. * - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
  79. *
  80. * @param $value mixed The value to be encoded
  81. * @return string Encoded value
  82. */
  83. protected function _encodeValue(&$value)
  84. {
  85. if (is_object($value)) {
  86. return $this->_encodeObject($value);
  87. } else if (is_array($value)) {
  88. return $this->_encodeArray($value);
  89. }
  90. return $this->_encodeDatum($value);
  91. }
  92. /**
  93. * Encode an object to JSON by encoding each of the public properties
  94. *
  95. * A special property is added to the JSON object called '__className'
  96. * that contains the name of the class of $value. This is used to decode
  97. * the object on the client into a specific class.
  98. *
  99. * @param $value object
  100. * @return string
  101. * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
  102. */
  103. protected function _encodeObject(&$value)
  104. {
  105. if ($this->_cycleCheck) {
  106. if ($this->_wasVisited($value)) {
  107. if (isset($this->_options['silenceCyclicalExceptions'])
  108. && $this->_options['silenceCyclicalExceptions']===true) {
  109. return '"* RECURSION (' . get_class($value) . ') *"';
  110. } else {
  111. require_once 'Zend/Json/Exception.php';
  112. throw new Zend_Json_Exception(
  113. 'Cycles not supported in JSON encoding, cycle introduced by '
  114. . 'class "' . get_class($value) . '"'
  115. );
  116. }
  117. }
  118. $this->_visited[] = $value;
  119. }
  120. $props = '';
  121. if ($value instanceof Iterator) {
  122. $propCollection = $value;
  123. } else {
  124. $propCollection = get_object_vars($value);
  125. }
  126. foreach ($propCollection as $name => $propValue) {
  127. if (isset($propValue)) {
  128. $props .= ','
  129. . $this->_encodeValue($name)
  130. . ':'
  131. . $this->_encodeValue($propValue);
  132. }
  133. }
  134. return '{"__className":"' . get_class($value) . '"'
  135. . $props . '}';
  136. }
  137. /**
  138. * Determine if an object has been serialized already
  139. *
  140. * @param mixed $value
  141. * @return boolean
  142. */
  143. protected function _wasVisited(&$value)
  144. {
  145. if (in_array($value, $this->_visited, true)) {
  146. return true;
  147. }
  148. return false;
  149. }
  150. /**
  151. * JSON encode an array value
  152. *
  153. * Recursively encodes each value of an array and returns a JSON encoded
  154. * array string.
  155. *
  156. * Arrays are defined as integer-indexed arrays starting at index 0, where
  157. * the last index is (count($array) -1); any deviation from that is
  158. * considered an associative array, and will be encoded as such.
  159. *
  160. * @param $array array
  161. * @return string
  162. */
  163. protected function _encodeArray(&$array)
  164. {
  165. $tmpArray = array();
  166. // Check for associative array
  167. if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
  168. // Associative array
  169. $result = '{';
  170. foreach ($array as $key => $value) {
  171. $key = (string) $key;
  172. $tmpArray[] = $this->_encodeString($key)
  173. . ':'
  174. . $this->_encodeValue($value);
  175. }
  176. $result .= implode(',', $tmpArray);
  177. $result .= '}';
  178. } else {
  179. // Indexed array
  180. $result = '[';
  181. $length = count($array);
  182. for ($i = 0; $i < $length; $i++) {
  183. $tmpArray[] = $this->_encodeValue($array[$i]);
  184. }
  185. $result .= implode(',', $tmpArray);
  186. $result .= ']';
  187. }
  188. return $result;
  189. }
  190. /**
  191. * JSON encode a basic data type (string, number, boolean, null)
  192. *
  193. * If value type is not a string, number, boolean, or null, the string
  194. * 'null' is returned.
  195. *
  196. * @param $value mixed
  197. * @return string
  198. */
  199. protected function _encodeDatum(&$value)
  200. {
  201. $result = 'null';
  202. if (is_int($value) || is_float($value)) {
  203. $result = (string) $value;
  204. $result = str_replace(",", ".", $result);
  205. } elseif (is_string($value)) {
  206. $result = $this->_encodeString($value);
  207. } elseif (is_bool($value)) {
  208. $result = $value ? 'true' : 'false';
  209. }
  210. return $result;
  211. }
  212. /**
  213. * JSON encode a string value by escaping characters as necessary
  214. *
  215. * @param $value string
  216. * @return string
  217. */
  218. protected function _encodeString(&$string)
  219. {
  220. // Escape these characters with a backslash:
  221. // " \ / \n \r \t \b \f
  222. $search = array('\\', "\n", "\t", "\r", "\b", "\f", '"');
  223. $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
  224. $string = str_replace($search, $replace, $string);
  225. // Escape certain ASCII characters:
  226. // 0x08 => \b
  227. // 0x0c => \f
  228. $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
  229. $string = self::encodeUnicodeString($string);
  230. return '"' . $string . '"';
  231. }
  232. /**
  233. * Encode the constants associated with the ReflectionClass
  234. * parameter. The encoding format is based on the class2 format
  235. *
  236. * @param $cls ReflectionClass
  237. * @return string Encoded constant block in class2 format
  238. */
  239. private static function _encodeConstants(ReflectionClass $cls)
  240. {
  241. $result = "constants : {";
  242. $constants = $cls->getConstants();
  243. $tmpArray = array();
  244. if (!empty($constants)) {
  245. foreach ($constants as $key => $value) {
  246. $tmpArray[] = "$key: " . self::encode($value);
  247. }
  248. $result .= implode(', ', $tmpArray);
  249. }
  250. return $result . "}";
  251. }
  252. /**
  253. * Encode the public methods of the ReflectionClass in the
  254. * class2 format
  255. *
  256. * @param $cls ReflectionClass
  257. * @return string Encoded method fragment
  258. *
  259. */
  260. private static function _encodeMethods(ReflectionClass $cls)
  261. {
  262. $methods = $cls->getMethods();
  263. $result = 'methods:{';
  264. $started = false;
  265. foreach ($methods as $method) {
  266. if (! $method->isPublic() || !$method->isUserDefined()) {
  267. continue;
  268. }
  269. if ($started) {
  270. $result .= ',';
  271. }
  272. $started = true;
  273. $result .= '' . $method->getName(). ':function(';
  274. if ('__construct' != $method->getName()) {
  275. $parameters = $method->getParameters();
  276. $paramCount = count($parameters);
  277. $argsStarted = false;
  278. $argNames = "var argNames=[";
  279. foreach ($parameters as $param) {
  280. if ($argsStarted) {
  281. $result .= ',';
  282. }
  283. $result .= $param->getName();
  284. if ($argsStarted) {
  285. $argNames .= ',';
  286. }
  287. $argNames .= '"' . $param->getName() . '"';
  288. $argsStarted = true;
  289. }
  290. $argNames .= "];";
  291. $result .= "){"
  292. . $argNames
  293. . 'var result = ZAjaxEngine.invokeRemoteMethod('
  294. . "this, '" . $method->getName()
  295. . "',argNames,arguments);"
  296. . 'return(result);}';
  297. } else {
  298. $result .= "){}";
  299. }
  300. }
  301. return $result . "}";
  302. }
  303. /**
  304. * Encode the public properties of the ReflectionClass in the class2
  305. * format.
  306. *
  307. * @param $cls ReflectionClass
  308. * @return string Encode properties list
  309. *
  310. */
  311. private static function _encodeVariables(ReflectionClass $cls)
  312. {
  313. $properties = $cls->getProperties();
  314. $propValues = get_class_vars($cls->getName());
  315. $result = "variables:{";
  316. $cnt = 0;
  317. $tmpArray = array();
  318. foreach ($properties as $prop) {
  319. if (! $prop->isPublic()) {
  320. continue;
  321. }
  322. $tmpArray[] = $prop->getName()
  323. . ':'
  324. . self::encode($propValues[$prop->getName()]);
  325. }
  326. $result .= implode(',', $tmpArray);
  327. return $result . "}";
  328. }
  329. /**
  330. * Encodes the given $className into the class2 model of encoding PHP
  331. * classes into JavaScript class2 classes.
  332. * NOTE: Currently only public methods and variables are proxied onto
  333. * the client machine
  334. *
  335. * @param $className string The name of the class, the class must be
  336. * instantiable using a null constructor
  337. * @param $package string Optional package name appended to JavaScript
  338. * proxy class name
  339. * @return string The class2 (JavaScript) encoding of the class
  340. * @throws Zend_Json_Exception
  341. */
  342. public static function encodeClass($className, $package = '')
  343. {
  344. $cls = new ReflectionClass($className);
  345. if (! $cls->isInstantiable()) {
  346. require_once 'Zend/Json/Exception.php';
  347. throw new Zend_Json_Exception("$className must be instantiable");
  348. }
  349. return "Class.create('$package$className',{"
  350. . self::_encodeConstants($cls) .","
  351. . self::_encodeMethods($cls) .","
  352. . self::_encodeVariables($cls) .'});';
  353. }
  354. /**
  355. * Encode several classes at once
  356. *
  357. * Returns JSON encoded classes, using {@link encodeClass()}.
  358. *
  359. * @param array $classNames
  360. * @param string $package
  361. * @return string
  362. */
  363. public static function encodeClasses(array $classNames, $package = '')
  364. {
  365. $result = '';
  366. foreach ($classNames as $className) {
  367. $result .= self::encodeClass($className, $package);
  368. }
  369. return $result;
  370. }
  371. /**
  372. * Encode Unicode Characters to \u0000 ASCII syntax.
  373. *
  374. * This algorithm was originally developed for the
  375. * Solar Framework by Paul M. Jones
  376. *
  377. * @link http://solarphp.com/
  378. * @link http://svn.solarphp.com/core/trunk/Solar/Json.php
  379. * @param string $value
  380. * @return string
  381. */
  382. public static function encodeUnicodeString($value)
  383. {
  384. $strlen_var = strlen($value);
  385. $ascii = "";
  386. /**
  387. * Iterate over every character in the string,
  388. * escaping with a slash or encoding to UTF-8 where necessary
  389. */
  390. for($i = 0; $i < $strlen_var; $i++) {
  391. $ord_var_c = ord($value[$i]);
  392. switch (true) {
  393. case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
  394. // characters U-00000000 - U-0000007F (same as ASCII)
  395. $ascii .= $value[$i];
  396. break;
  397. case (($ord_var_c & 0xE0) == 0xC0):
  398. // characters U-00000080 - U-000007FF, mask 110XXXXX
  399. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  400. $char = pack('C*', $ord_var_c, ord($value[$i + 1]));
  401. $i += 1;
  402. $utf16 = self::_utf82utf16($char);
  403. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  404. break;
  405. case (($ord_var_c & 0xF0) == 0xE0):
  406. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  407. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  408. $char = pack('C*', $ord_var_c,
  409. ord($value[$i + 1]),
  410. ord($value[$i + 2]));
  411. $i += 2;
  412. $utf16 = self::_utf82utf16($char);
  413. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  414. break;
  415. case (($ord_var_c & 0xF8) == 0xF0):
  416. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  417. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  418. $char = pack('C*', $ord_var_c,
  419. ord($value[$i + 1]),
  420. ord($value[$i + 2]),
  421. ord($value[$i + 3]));
  422. $i += 3;
  423. $utf16 = self::_utf82utf16($char);
  424. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  425. break;
  426. case (($ord_var_c & 0xFC) == 0xF8):
  427. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  428. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  429. $char = pack('C*', $ord_var_c,
  430. ord($value[$i + 1]),
  431. ord($value[$i + 2]),
  432. ord($value[$i + 3]),
  433. ord($value[$i + 4]));
  434. $i += 4;
  435. $utf16 = self::_utf82utf16($char);
  436. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  437. break;
  438. case (($ord_var_c & 0xFE) == 0xFC):
  439. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  440. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  441. $char = pack('C*', $ord_var_c,
  442. ord($value[$i + 1]),
  443. ord($value[$i + 2]),
  444. ord($value[$i + 3]),
  445. ord($value[$i + 4]),
  446. ord($value[$i + 5]));
  447. $i += 5;
  448. $utf16 = self::_utf82utf16($char);
  449. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  450. break;
  451. }
  452. }
  453. return $ascii;
  454. }
  455. /**
  456. * Convert a string from one UTF-8 char to one UTF-16 char.
  457. *
  458. * Normally should be handled by mb_convert_encoding, but
  459. * provides a slower PHP-only method for installations
  460. * that lack the multibye string extension.
  461. *
  462. * This method is from the Solar Framework by Paul M. Jones
  463. *
  464. * @link http://solarphp.com
  465. * @param string $utf8 UTF-8 character
  466. * @return string UTF-16 character
  467. */
  468. protected static function _utf82utf16($utf8)
  469. {
  470. // Check for mb extension otherwise do by hand.
  471. if( function_exists('mb_convert_encoding') ) {
  472. return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  473. }
  474. switch (strlen($utf8)) {
  475. case 1:
  476. // this case should never be reached, because we are in ASCII range
  477. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  478. return $utf8;
  479. case 2:
  480. // return a UTF-16 character from a 2-byte UTF-8 char
  481. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  482. return chr(0x07 & (ord($utf8{0}) >> 2))
  483. . chr((0xC0 & (ord($utf8{0}) << 6))
  484. | (0x3F & ord($utf8{1})));
  485. case 3:
  486. // return a UTF-16 character from a 3-byte UTF-8 char
  487. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  488. return chr((0xF0 & (ord($utf8{0}) << 4))
  489. | (0x0F & (ord($utf8{1}) >> 2)))
  490. . chr((0xC0 & (ord($utf8{1}) << 6))
  491. | (0x7F & ord($utf8{2})));
  492. }
  493. // ignoring UTF-32 for now, sorry
  494. return '';
  495. }
  496. }