PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/monica/vendor/zendframework/zendframework/library/Zend/Json/Encoder.php

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