PageRenderTime 25ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/rokcommon/RokCommon/JSON.php

https://bitbucket.org/pastor399/newcastleunifc
PHP | 539 lines | 298 code | 54 blank | 187 comment | 71 complexity | 9a6b305f993eaf0f6bd7d25bc671e79e MD5 | raw file
  1. <?php
  2. /**
  3. * @version $Id: JSON.php 57548 2012-10-15 01:51:49Z btowles $
  4. * @author RocketTheme http://www.rockettheme.com
  5. * @copyright Copyright (C) 2007 - ${copyright_year} RocketTheme, LLC
  6. * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 only
  7. */
  8. defined('ROKCOMMON') or die;
  9. class RokCommon_JSON_Exception extends Exception
  10. {
  11. }
  12. /**
  13. * RokCommon_Annotation to Ignore a Property on JSON Encode
  14. * @Target("property")
  15. */
  16. class RokCommon_JSON_Annotation_JSONEncodeIgnore extends RokCommon_Annotation
  17. {
  18. }
  19. /**
  20. * RokCommon_Annotation to Ignore a Property on JSON Decode
  21. * @Target("property")
  22. */
  23. class RokCommon_JSON_Annotation_JSONDecodeIgnore extends RokCommon_Annotation
  24. {
  25. }
  26. /**
  27. * RokCommon_Annotation to Ignore a Property on JSON Decode
  28. * @Target("class")
  29. */
  30. class RokCommon_JSON_Annotation_JSONDefaultKey extends RokCommon_Annotation
  31. {
  32. /**
  33. * @param RokCommon_Annotation_ReflectionClass $target
  34. */
  35. protected function checkConstraints($target)
  36. {
  37. if (!$target->hasProperty($this->value)) {
  38. trigger_error(sprintf('Class %s has a JSONDefaultKey annotation which references a property \'%s\' which does not exist.', $target->getName(), $this->value), E_USER_ERROR);
  39. }
  40. }
  41. }
  42. /**
  43. *
  44. */
  45. class RokCommon_JSON
  46. {
  47. const TYPE_FIELD = '_type';
  48. /**
  49. * @var array
  50. */
  51. protected static $cache = array();
  52. /**
  53. * A wrapper for json_encode
  54. * @static
  55. *
  56. * @param mixed $data
  57. *
  58. * @return string
  59. */
  60. public static function encode($data)
  61. {
  62. return json_encode(self::prepDataForEncode($data));
  63. }
  64. /**
  65. * @static
  66. *
  67. * @param $data
  68. *
  69. * @return array|stdClass
  70. */
  71. protected static function prepDataForEncode($data)
  72. {
  73. if (is_object($data)) {
  74. $json_preped_encode = new stdClass();
  75. $class = new RokCommon_Annotation_ReflectionClass($data);
  76. $class_type_field = self::TYPE_FIELD;
  77. $json_preped_encode->$class_type_field = $class->getName();
  78. /** @var $properties ReflectionAnnotatedProperty[] */
  79. $properties = $class->getProperties();
  80. foreach ($properties as $property) {
  81. if (!$property->hasAnnotation('RokCommon_JSON_Annotation_JSONEncodeIgnore') && !$property->isStatic()) {
  82. $name = $property->getName();
  83. $json_preped_encode->$name = self::prepDataForEncode(self::getPropertyValue($data, $property));
  84. }
  85. }
  86. } elseif (is_array($data)) {
  87. $json_preped_encode = array();
  88. foreach ($data as $key => $value) {
  89. $json_preped_encode[$key] = self::prepDataForEncode($value);
  90. }
  91. } else {
  92. $json_preped_encode = $data;
  93. }
  94. return $json_preped_encode;
  95. }
  96. /**
  97. * @static
  98. *
  99. * @param $object
  100. * @param RokCommon_Annotation_ReflectionProperty $property
  101. *
  102. * @return mixed|null
  103. */
  104. protected static function getPropertyValue(&$object, RokCommon_Annotation_ReflectionProperty &$property)
  105. {
  106. $out = null;
  107. $tried = false;
  108. if (empty($out)) {
  109. if ($property->isPublic() && !$property->isStatic()) {
  110. $out = $property->getValue($object);
  111. $tried = true;
  112. }
  113. }
  114. if (empty($out) && !$tried) {
  115. if ($property->getDeclaringClass()->hasMethod('__get') && $property->getDeclaringClass()->getMethod('__get')->isPublic() && !$property->getDeclaringClass()->getMethod('__get')->isStatic()) {
  116. $property_name = $property->getName();
  117. $out = $object->$property_name;
  118. $tried = true;
  119. }
  120. }
  121. if (empty($out) && !$tried){
  122. $out = self::getPropertyValueFromGetter($object, $property);
  123. $tried = true;
  124. }
  125. return $out;
  126. }
  127. /**
  128. * @static
  129. *
  130. * @param $object
  131. * @param RokCommon_Annotation_ReflectionProperty $property
  132. *
  133. * @return mixed|null
  134. */
  135. protected static function getPropertyValueFromGetter(&$object, RokCommon_Annotation_ReflectionProperty &$property)
  136. {
  137. /*
  138. * See if the property has a setter and use that setter if it only has one parameters or
  139. * if the following parameters are all optional
  140. */
  141. $getter_name = 'get' . ucfirst(preg_replace('/^\W+/', '', $property->getName()));
  142. if (!$property->getDeclaringClass()->hasMethod($getter_name)) {
  143. return null;
  144. }
  145. $getter = $property->getDeclaringClass()->getMethod($getter_name);
  146. if (!($getter->isPublic() && !$getter->isStatic())) // Only use a public no static setter
  147. {
  148. return null;
  149. }
  150. /** @var $getter_params ReflectionParameter[] */
  151. $getter_params = $getter->getParameters();
  152. if (count($getter_params) > 0) {
  153. reset($getter_params); // reset to first parameter
  154. /** @var $checking_param ReflectionParameter */
  155. $checking_param = current($getter_params); // get the first
  156. while ($checking_param = next($getter_params)) {
  157. if (!$checking_param->isOptional()) {
  158. return null;
  159. }
  160. }
  161. }
  162. // call the getter with the property value
  163. return $getter->invoke($object);
  164. }
  165. /**
  166. * A wrapper for json_decode that will also map the decoded json string to an object of a specific class if the classname is passed in.
  167. *
  168. * @static
  169. *
  170. * @param string $json
  171. * @param string $classname
  172. * @param bool $assoc
  173. * @param bool $strict
  174. *
  175. * @return mixed
  176. */
  177. public static function decode($json, $classname = null, $assoc = false, $strict = false)
  178. {
  179. $_assoc = (null != $classname) ? true : $assoc;
  180. $decoded = json_decode($json, $_assoc);
  181. if (null == $decoded) {
  182. throw new RokCommon_JSON_Exception('Error decoding JSON string');
  183. }
  184. if (null != $classname) {
  185. $decoded = self::decodeToObject($decoded, $classname, $strict);
  186. }
  187. return $decoded;
  188. }
  189. /**
  190. * Recursive method to decode a JSON decoded array into an instance of the provided class
  191. * @static
  192. *
  193. * @param array $json_array
  194. * @param string $classname
  195. * @param bool $strict
  196. *
  197. * @return mixed
  198. * @throws RokCommon_JSON_Exception
  199. */
  200. protected static function decodeToObject(array $json_array, $classname, $strict)
  201. {
  202. if (!class_exists($classname)) {
  203. throw new RokCommon_JSON_Exception('Unable to load class: ' . $classname);
  204. }
  205. // Make sure the constructor for the object being deserialized is no argument or all defaulted
  206. $class = new RokCommon_Annotation_ReflectionClass($classname);
  207. /** @var $constructor RokCommon_Annotation_ReflectionMethod */
  208. $constructor = $class->getConstructor();
  209. if (null != $constructor) {
  210. $all_optional = true;
  211. /** @var $constructor_params ReflectionParameter[] */
  212. $constructor_params = $constructor->getParameters();
  213. if (!empty($constructor_params)) {
  214. foreach ($constructor_params as $constructor_param) {
  215. if (!$constructor_param->isOptional()) {
  216. $all_optional = false;
  217. }
  218. }
  219. }
  220. if (!$all_optional) {
  221. throw new RokCommon_JSON_Exception('Classes used with JSON unserialize need to have default or no argument constructor: failed for ' . $classname);
  222. }
  223. }
  224. // make new instance to deserialize into
  225. $mapped_object = $class->newInstance();
  226. foreach ($json_array as $json_key_name => $json_value) {
  227. // Throw an exception if there is no matching property on the class
  228. if (!$class->hasProperty($json_key_name)) {
  229. if ($strict) {
  230. throw new RokCommon_JSON_Exception(sprintf('JSON value does not have matching property %s on class %s', $json_key_name, $classname));
  231. } else {
  232. continue;
  233. }
  234. }
  235. $property = $class->getProperty($json_key_name);
  236. // See if we need to ignore this Decode
  237. if ($property->hasAnnotation('RokCommon_JSON_Annotation_JSONDecodeIgnore')) {
  238. continue;
  239. }
  240. if (is_array($json_value)) {
  241. }
  242. /*
  243. * Handle the different types of properties
  244. */
  245. if (self::isPropertyAScalar($property)) {
  246. //If the property is a scalar or scalar array just set the value
  247. self::setPropertyValue($mapped_object, $property, $json_value);
  248. } else {
  249. if (self::isPropertyAnArray($property) && !is_array($json_value)) {
  250. throw new RokCommon_JSON_Exception(sprintf('JSON value \'%s\' is not an array or object as expected for property %s on class %s', $json_value, $property->getName(), $class->getName()));
  251. } elseif (self::isPropertyAnArray($property) && is_array($json_value)) {
  252. $unserlized_array = array();
  253. foreach ($json_value as $json_decode_object_key => $json_decode_object_value_array) {
  254. // get the object from the array
  255. $decoded_object = self::decodeToObject($json_decode_object_value_array, self::getPropertyType($property), $strict);
  256. // if there is a default key property for the object class set it on the object
  257. $default_key_property = self::getDefaultKeyProperty(new RokCommon_Annotation_ReflectionClass(self::getPropertyType($property)));
  258. if ($default_key_property) {
  259. self::setPropertyValue($decoded_object, $default_key_property, $json_decode_object_key);
  260. }
  261. // add the object to the array
  262. $unserlized_array[$json_decode_object_key] = $decoded_object;
  263. }
  264. // set the array of objects to the current property
  265. self::setPropertyValue($mapped_object, $property, $unserlized_array);
  266. } else {
  267. // property is not an array but the json value is
  268. // means that its a single object of the property type
  269. self::setPropertyValue($mapped_object, $property, self::decodeToObject($json_value, self::getPropertyType($property), $strict));
  270. }
  271. }
  272. }
  273. return $mapped_object;
  274. }
  275. /**
  276. * @param object $object
  277. * @param RokCommon_Annotation_ReflectionProperty $property
  278. * @param mixed $value
  279. *
  280. * @return bool
  281. */
  282. protected function setPropertyValue(&$object, RokCommon_Annotation_ReflectionProperty &$property, $value)
  283. {
  284. /*
  285. * Try to set by Setter
  286. */
  287. if (self::setPropertyBySetter($value, $property, $object)) {
  288. return true;
  289. }
  290. /*
  291. * Ok, that didnt work lets try the direct approach
  292. */
  293. if ($property->isPublic() && !$property->isStatic()) {
  294. $property->setValue($object, $value);
  295. return true;
  296. }
  297. /*
  298. * well we cant set directly lets try a magic method
  299. */
  300. if ($property->getDeclaringClass()->hasMethod('__set') && $property->getDeclaringClass()->getMethod('__set')->isPublic() && !$property->getDeclaringClass()->getMethod('__set')->isStatic()) {
  301. $property_name = $property->getName();
  302. $object->$property_name = $value;
  303. return true;
  304. }
  305. // well we tried
  306. return false;
  307. }
  308. /**
  309. * @param RokCommon_Annotation_ReflectionClass $class
  310. *
  311. * @return bool|RokCommon_Annotation_ReflectionProperty
  312. */
  313. protected static function &getDefaultKeyProperty(RokCommon_Annotation_ReflectionClass &$class)
  314. {
  315. if (!isset(self::$cache[$class->getName()]['_default_key_'])) {
  316. self::$cache[$class->getName()]['_default_key_'] = false;
  317. if ($class->hasAnnotation('RokCommon_JSON_Annotation_JSONDefaultKey')) {
  318. $property = $class->getProperty($class->getAnnotation('RokCommon_JSON_Annotation_JSONDefaultKey')->value);
  319. self::$cache[$class->getName()]['_default_key_'] = $property;
  320. }
  321. }
  322. return self::$cache[$class->getName()]['_default_key_'];
  323. }
  324. /**
  325. * Set the value of the property using a setter if the setter is public, non static, and has only one parameter or
  326. * if the following parameters are all optional
  327. *
  328. * @param mixed $value
  329. * @param RokCommon_Annotation_ReflectionProperty $property
  330. * @param object $object
  331. *
  332. * @return bool true if the value was set by the setter, false if not
  333. */
  334. protected static function setPropertyBySetter($value, RokCommon_Annotation_ReflectionProperty &$property, &$object)
  335. {
  336. /*
  337. * See if the property has a setter and use that setter if it only has one parameters or
  338. * if the following parameters are all optional
  339. */
  340. $setter_name = 'set' . ucfirst(preg_replace('/^\W+/', '', $property->getName()));
  341. if (!$property->getDeclaringClass()->hasMethod($setter_name)) {
  342. return false;
  343. }
  344. $setter = $property->getDeclaringClass()->getMethod($setter_name);
  345. if (!($setter->isPublic() && !$setter->isStatic())) // Only use a public no static setter
  346. {
  347. return false;
  348. }
  349. /** @var $setter_params ReflectionParameter[] */
  350. $setter_params = $setter->getParameters();
  351. if (count($setter_params) > 1) {
  352. reset($setter_params); // reset to first parameter
  353. /** @var $checking_param ReflectionParameter */
  354. $checking_param = next($setter_params); // skip the first param
  355. do {
  356. if (!$checking_param->isOptional()) {
  357. return false;
  358. }
  359. } while ($checking_param = next($setter_params));
  360. }
  361. // call the setter with the property value
  362. $setter->invoke($object, $value);
  363. return true;
  364. }
  365. /**
  366. * Helper function to determine if a passed data type description is a scalar data type.
  367. * @static
  368. *
  369. * @param \ReflectionProperty $property
  370. *
  371. * @return bool
  372. */
  373. protected static function isPropertyAScalar(ReflectionProperty &$property)
  374. {
  375. $prop_info = self::getPropertyInfoFromDocs($property);
  376. switch (strtolower($prop_info->type)) {
  377. case 'int':
  378. case 'integer':
  379. case 'bool':
  380. case 'boolean':
  381. case 'string':
  382. case 'float':
  383. case 'double':
  384. case 'number':
  385. return true;
  386. default:
  387. return false;
  388. }
  389. }
  390. /**
  391. * @static
  392. *
  393. * @param \ReflectionProperty $property
  394. *
  395. * @return bool
  396. */
  397. protected static function isPropertyAnArray(ReflectionProperty &$property)
  398. {
  399. $prop_info = self::getPropertyInfoFromDocs($property);
  400. return $prop_info->array;
  401. }
  402. /**
  403. * Get the type of the property form the @var doc tag
  404. * @static
  405. *
  406. * @param ReflectionProperty $property
  407. *
  408. * @return mixed
  409. */
  410. protected static function getPropertyType(ReflectionProperty &$property)
  411. {
  412. $prop_info = self::getPropertyInfoFromDocs($property);
  413. return $prop_info->type;
  414. }
  415. //protected static function getPropertyInfo()
  416. /**
  417. * Method to get the data type form the PHPDoc block of a class property
  418. * @static
  419. *
  420. * @param ReflectionProperty $property
  421. *
  422. * @return null|string
  423. */
  424. protected static function getPropertyInfoFromDocs(ReflectionProperty &$property)
  425. {
  426. // See if its in the cache and if not process
  427. if (!isset(self::$cache[$property->getDeclaringClass()->getName()][$property->getName()])) {
  428. $docComment = $property->getDocComment();
  429. if (trim($docComment) == '') {
  430. return null;
  431. }
  432. $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
  433. $docComment = ltrim($docComment, "\r\n");
  434. $docComment = rtrim($docComment, "*/");
  435. if (substr_count($docComment, "\n") == 0) $docComment .= "\n";
  436. $parsedDocComment = $docComment;
  437. $lineNumber = $firstBlandLineEncountered = 0;
  438. $results = new stdClass();
  439. $results->full_type = 'stdClass';
  440. $results->type = 'stdClass';
  441. $results->array = false;
  442. while (($newlinePos = strpos($parsedDocComment, "\n")) !== false) {
  443. $lineNumber++;
  444. $line = substr($parsedDocComment, 0, $newlinePos);
  445. $content_matches = array();
  446. if ((strpos($line, '@') === 0) && (preg_match('#^(@\w+.*?)(\n)(?:@|\r?\n|$)#s', $parsedDocComment, $content_matches))) {
  447. $tagDocblockLine = $content_matches[1];
  448. $tag_matches = array();
  449. if (!preg_match('#^@(\w+)(\s|$)#', $tagDocblockLine, $tag_matches)) {
  450. break;
  451. }
  452. $type_matches = array();
  453. if (!preg_match('#^@(\w+)\s+([\w|\\\]+[\[\]]*)(?:\s+(\$\S+))?(?:\s+(.*))?#s', $tagDocblockLine, $type_matches)) {
  454. break;
  455. }
  456. if (strtolower($type_matches[1]) == 'var') {
  457. $results->full_type = $type_matches[2];
  458. break;
  459. }
  460. $parsedDocComment = str_replace($content_matches[1] . $content_matches[2], '', $parsedDocComment);
  461. }
  462. }
  463. // check if its an array;
  464. $array_matches = array();
  465. preg_match('#^([\w|\\\]+)(\[\])?$#', $results->full_type, $array_matches);
  466. $results->type = $array_matches[1];
  467. $results->array = isset($array_matches[2]);
  468. // set the cached value
  469. self::$cache[$property->getDeclaringClass()->getName()][$property->getName()] = $results;
  470. }
  471. return self::$cache[$property->getDeclaringClass()->getName()][$property->getName()];
  472. }
  473. /**
  474. * @param $errno
  475. * @param $errstr
  476. * @param $errfile
  477. * @param $errline
  478. *
  479. * @throws ErrorException
  480. */
  481. public function exception_error_handler($errno, $errstr, $errfile, $errline)
  482. {
  483. throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
  484. }
  485. }