/libraries/rokcommon/RokCommon/JSON.php
PHP | 539 lines | 298 code | 54 blank | 187 comment | 71 complexity | 9a6b305f993eaf0f6bd7d25bc671e79e MD5 | raw file
- <?php
- /**
- * @version $Id: JSON.php 57548 2012-10-15 01:51:49Z btowles $
- * @author RocketTheme http://www.rockettheme.com
- * @copyright Copyright (C) 2007 - ${copyright_year} RocketTheme, LLC
- * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 only
- */
- defined('ROKCOMMON') or die;
- class RokCommon_JSON_Exception extends Exception
- {
- }
- /**
- * RokCommon_Annotation to Ignore a Property on JSON Encode
- * @Target("property")
- */
- class RokCommon_JSON_Annotation_JSONEncodeIgnore extends RokCommon_Annotation
- {
- }
- /**
- * RokCommon_Annotation to Ignore a Property on JSON Decode
- * @Target("property")
- */
- class RokCommon_JSON_Annotation_JSONDecodeIgnore extends RokCommon_Annotation
- {
- }
- /**
- * RokCommon_Annotation to Ignore a Property on JSON Decode
- * @Target("class")
- */
- class RokCommon_JSON_Annotation_JSONDefaultKey extends RokCommon_Annotation
- {
- /**
- * @param RokCommon_Annotation_ReflectionClass $target
- */
- protected function checkConstraints($target)
- {
- if (!$target->hasProperty($this->value)) {
- 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);
- }
- }
- }
- /**
- *
- */
- class RokCommon_JSON
- {
- const TYPE_FIELD = '_type';
- /**
- * @var array
- */
- protected static $cache = array();
- /**
- * A wrapper for json_encode
- * @static
- *
- * @param mixed $data
- *
- * @return string
- */
- public static function encode($data)
- {
- return json_encode(self::prepDataForEncode($data));
- }
- /**
- * @static
- *
- * @param $data
- *
- * @return array|stdClass
- */
- protected static function prepDataForEncode($data)
- {
- if (is_object($data)) {
- $json_preped_encode = new stdClass();
- $class = new RokCommon_Annotation_ReflectionClass($data);
- $class_type_field = self::TYPE_FIELD;
- $json_preped_encode->$class_type_field = $class->getName();
- /** @var $properties ReflectionAnnotatedProperty[] */
- $properties = $class->getProperties();
- foreach ($properties as $property) {
- if (!$property->hasAnnotation('RokCommon_JSON_Annotation_JSONEncodeIgnore') && !$property->isStatic()) {
- $name = $property->getName();
- $json_preped_encode->$name = self::prepDataForEncode(self::getPropertyValue($data, $property));
- }
- }
- } elseif (is_array($data)) {
- $json_preped_encode = array();
- foreach ($data as $key => $value) {
- $json_preped_encode[$key] = self::prepDataForEncode($value);
- }
- } else {
- $json_preped_encode = $data;
- }
- return $json_preped_encode;
- }
- /**
- * @static
- *
- * @param $object
- * @param RokCommon_Annotation_ReflectionProperty $property
- *
- * @return mixed|null
- */
- protected static function getPropertyValue(&$object, RokCommon_Annotation_ReflectionProperty &$property)
- {
- $out = null;
- $tried = false;
- if (empty($out)) {
- if ($property->isPublic() && !$property->isStatic()) {
- $out = $property->getValue($object);
- $tried = true;
- }
- }
- if (empty($out) && !$tried) {
- if ($property->getDeclaringClass()->hasMethod('__get') && $property->getDeclaringClass()->getMethod('__get')->isPublic() && !$property->getDeclaringClass()->getMethod('__get')->isStatic()) {
- $property_name = $property->getName();
- $out = $object->$property_name;
- $tried = true;
- }
- }
- if (empty($out) && !$tried){
- $out = self::getPropertyValueFromGetter($object, $property);
- $tried = true;
- }
- return $out;
- }
- /**
- * @static
- *
- * @param $object
- * @param RokCommon_Annotation_ReflectionProperty $property
- *
- * @return mixed|null
- */
- protected static function getPropertyValueFromGetter(&$object, RokCommon_Annotation_ReflectionProperty &$property)
- {
- /*
- * See if the property has a setter and use that setter if it only has one parameters or
- * if the following parameters are all optional
- */
- $getter_name = 'get' . ucfirst(preg_replace('/^\W+/', '', $property->getName()));
- if (!$property->getDeclaringClass()->hasMethod($getter_name)) {
- return null;
- }
- $getter = $property->getDeclaringClass()->getMethod($getter_name);
- if (!($getter->isPublic() && !$getter->isStatic())) // Only use a public no static setter
- {
- return null;
- }
- /** @var $getter_params ReflectionParameter[] */
- $getter_params = $getter->getParameters();
- if (count($getter_params) > 0) {
- reset($getter_params); // reset to first parameter
- /** @var $checking_param ReflectionParameter */
- $checking_param = current($getter_params); // get the first
- while ($checking_param = next($getter_params)) {
- if (!$checking_param->isOptional()) {
- return null;
- }
- }
- }
- // call the getter with the property value
- return $getter->invoke($object);
- }
- /**
- * 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.
- *
- * @static
- *
- * @param string $json
- * @param string $classname
- * @param bool $assoc
- * @param bool $strict
- *
- * @return mixed
- */
- public static function decode($json, $classname = null, $assoc = false, $strict = false)
- {
- $_assoc = (null != $classname) ? true : $assoc;
- $decoded = json_decode($json, $_assoc);
- if (null == $decoded) {
- throw new RokCommon_JSON_Exception('Error decoding JSON string');
- }
- if (null != $classname) {
- $decoded = self::decodeToObject($decoded, $classname, $strict);
- }
- return $decoded;
- }
- /**
- * Recursive method to decode a JSON decoded array into an instance of the provided class
- * @static
- *
- * @param array $json_array
- * @param string $classname
- * @param bool $strict
- *
- * @return mixed
- * @throws RokCommon_JSON_Exception
- */
- protected static function decodeToObject(array $json_array, $classname, $strict)
- {
- if (!class_exists($classname)) {
- throw new RokCommon_JSON_Exception('Unable to load class: ' . $classname);
- }
- // Make sure the constructor for the object being deserialized is no argument or all defaulted
- $class = new RokCommon_Annotation_ReflectionClass($classname);
- /** @var $constructor RokCommon_Annotation_ReflectionMethod */
- $constructor = $class->getConstructor();
- if (null != $constructor) {
- $all_optional = true;
- /** @var $constructor_params ReflectionParameter[] */
- $constructor_params = $constructor->getParameters();
- if (!empty($constructor_params)) {
- foreach ($constructor_params as $constructor_param) {
- if (!$constructor_param->isOptional()) {
- $all_optional = false;
- }
- }
- }
- if (!$all_optional) {
- throw new RokCommon_JSON_Exception('Classes used with JSON unserialize need to have default or no argument constructor: failed for ' . $classname);
- }
- }
- // make new instance to deserialize into
- $mapped_object = $class->newInstance();
- foreach ($json_array as $json_key_name => $json_value) {
- // Throw an exception if there is no matching property on the class
- if (!$class->hasProperty($json_key_name)) {
- if ($strict) {
- throw new RokCommon_JSON_Exception(sprintf('JSON value does not have matching property %s on class %s', $json_key_name, $classname));
- } else {
- continue;
- }
- }
- $property = $class->getProperty($json_key_name);
- // See if we need to ignore this Decode
- if ($property->hasAnnotation('RokCommon_JSON_Annotation_JSONDecodeIgnore')) {
- continue;
- }
- if (is_array($json_value)) {
- }
- /*
- * Handle the different types of properties
- */
- if (self::isPropertyAScalar($property)) {
- //If the property is a scalar or scalar array just set the value
- self::setPropertyValue($mapped_object, $property, $json_value);
- } else {
- if (self::isPropertyAnArray($property) && !is_array($json_value)) {
- 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()));
- } elseif (self::isPropertyAnArray($property) && is_array($json_value)) {
- $unserlized_array = array();
- foreach ($json_value as $json_decode_object_key => $json_decode_object_value_array) {
- // get the object from the array
- $decoded_object = self::decodeToObject($json_decode_object_value_array, self::getPropertyType($property), $strict);
- // if there is a default key property for the object class set it on the object
- $default_key_property = self::getDefaultKeyProperty(new RokCommon_Annotation_ReflectionClass(self::getPropertyType($property)));
- if ($default_key_property) {
- self::setPropertyValue($decoded_object, $default_key_property, $json_decode_object_key);
- }
- // add the object to the array
- $unserlized_array[$json_decode_object_key] = $decoded_object;
- }
- // set the array of objects to the current property
- self::setPropertyValue($mapped_object, $property, $unserlized_array);
- } else {
- // property is not an array but the json value is
- // means that its a single object of the property type
- self::setPropertyValue($mapped_object, $property, self::decodeToObject($json_value, self::getPropertyType($property), $strict));
- }
- }
- }
- return $mapped_object;
- }
- /**
- * @param object $object
- * @param RokCommon_Annotation_ReflectionProperty $property
- * @param mixed $value
- *
- * @return bool
- */
- protected function setPropertyValue(&$object, RokCommon_Annotation_ReflectionProperty &$property, $value)
- {
- /*
- * Try to set by Setter
- */
- if (self::setPropertyBySetter($value, $property, $object)) {
- return true;
- }
- /*
- * Ok, that didnt work lets try the direct approach
- */
- if ($property->isPublic() && !$property->isStatic()) {
- $property->setValue($object, $value);
- return true;
- }
- /*
- * well we cant set directly lets try a magic method
- */
- if ($property->getDeclaringClass()->hasMethod('__set') && $property->getDeclaringClass()->getMethod('__set')->isPublic() && !$property->getDeclaringClass()->getMethod('__set')->isStatic()) {
- $property_name = $property->getName();
- $object->$property_name = $value;
- return true;
- }
- // well we tried
- return false;
- }
- /**
- * @param RokCommon_Annotation_ReflectionClass $class
- *
- * @return bool|RokCommon_Annotation_ReflectionProperty
- */
- protected static function &getDefaultKeyProperty(RokCommon_Annotation_ReflectionClass &$class)
- {
- if (!isset(self::$cache[$class->getName()]['_default_key_'])) {
- self::$cache[$class->getName()]['_default_key_'] = false;
- if ($class->hasAnnotation('RokCommon_JSON_Annotation_JSONDefaultKey')) {
- $property = $class->getProperty($class->getAnnotation('RokCommon_JSON_Annotation_JSONDefaultKey')->value);
- self::$cache[$class->getName()]['_default_key_'] = $property;
- }
- }
- return self::$cache[$class->getName()]['_default_key_'];
- }
- /**
- * Set the value of the property using a setter if the setter is public, non static, and has only one parameter or
- * if the following parameters are all optional
- *
- * @param mixed $value
- * @param RokCommon_Annotation_ReflectionProperty $property
- * @param object $object
- *
- * @return bool true if the value was set by the setter, false if not
- */
- protected static function setPropertyBySetter($value, RokCommon_Annotation_ReflectionProperty &$property, &$object)
- {
- /*
- * See if the property has a setter and use that setter if it only has one parameters or
- * if the following parameters are all optional
- */
- $setter_name = 'set' . ucfirst(preg_replace('/^\W+/', '', $property->getName()));
- if (!$property->getDeclaringClass()->hasMethod($setter_name)) {
- return false;
- }
- $setter = $property->getDeclaringClass()->getMethod($setter_name);
- if (!($setter->isPublic() && !$setter->isStatic())) // Only use a public no static setter
- {
- return false;
- }
- /** @var $setter_params ReflectionParameter[] */
- $setter_params = $setter->getParameters();
- if (count($setter_params) > 1) {
- reset($setter_params); // reset to first parameter
- /** @var $checking_param ReflectionParameter */
- $checking_param = next($setter_params); // skip the first param
- do {
- if (!$checking_param->isOptional()) {
- return false;
- }
- } while ($checking_param = next($setter_params));
- }
- // call the setter with the property value
- $setter->invoke($object, $value);
- return true;
- }
- /**
- * Helper function to determine if a passed data type description is a scalar data type.
- * @static
- *
- * @param \ReflectionProperty $property
- *
- * @return bool
- */
- protected static function isPropertyAScalar(ReflectionProperty &$property)
- {
- $prop_info = self::getPropertyInfoFromDocs($property);
- switch (strtolower($prop_info->type)) {
- case 'int':
- case 'integer':
- case 'bool':
- case 'boolean':
- case 'string':
- case 'float':
- case 'double':
- case 'number':
- return true;
- default:
- return false;
- }
- }
- /**
- * @static
- *
- * @param \ReflectionProperty $property
- *
- * @return bool
- */
- protected static function isPropertyAnArray(ReflectionProperty &$property)
- {
- $prop_info = self::getPropertyInfoFromDocs($property);
- return $prop_info->array;
- }
- /**
- * Get the type of the property form the @var doc tag
- * @static
- *
- * @param ReflectionProperty $property
- *
- * @return mixed
- */
- protected static function getPropertyType(ReflectionProperty &$property)
- {
- $prop_info = self::getPropertyInfoFromDocs($property);
- return $prop_info->type;
- }
- //protected static function getPropertyInfo()
- /**
- * Method to get the data type form the PHPDoc block of a class property
- * @static
- *
- * @param ReflectionProperty $property
- *
- * @return null|string
- */
- protected static function getPropertyInfoFromDocs(ReflectionProperty &$property)
- {
- // See if its in the cache and if not process
- if (!isset(self::$cache[$property->getDeclaringClass()->getName()][$property->getName()])) {
- $docComment = $property->getDocComment();
- if (trim($docComment) == '') {
- return null;
- }
- $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
- $docComment = ltrim($docComment, "\r\n");
- $docComment = rtrim($docComment, "*/");
- if (substr_count($docComment, "\n") == 0) $docComment .= "\n";
- $parsedDocComment = $docComment;
- $lineNumber = $firstBlandLineEncountered = 0;
- $results = new stdClass();
- $results->full_type = 'stdClass';
- $results->type = 'stdClass';
- $results->array = false;
- while (($newlinePos = strpos($parsedDocComment, "\n")) !== false) {
- $lineNumber++;
- $line = substr($parsedDocComment, 0, $newlinePos);
- $content_matches = array();
- if ((strpos($line, '@') === 0) && (preg_match('#^(@\w+.*?)(\n)(?:@|\r?\n|$)#s', $parsedDocComment, $content_matches))) {
- $tagDocblockLine = $content_matches[1];
- $tag_matches = array();
- if (!preg_match('#^@(\w+)(\s|$)#', $tagDocblockLine, $tag_matches)) {
- break;
- }
- $type_matches = array();
- if (!preg_match('#^@(\w+)\s+([\w|\\\]+[\[\]]*)(?:\s+(\$\S+))?(?:\s+(.*))?#s', $tagDocblockLine, $type_matches)) {
- break;
- }
- if (strtolower($type_matches[1]) == 'var') {
- $results->full_type = $type_matches[2];
- break;
- }
- $parsedDocComment = str_replace($content_matches[1] . $content_matches[2], '', $parsedDocComment);
- }
- }
- // check if its an array;
- $array_matches = array();
- preg_match('#^([\w|\\\]+)(\[\])?$#', $results->full_type, $array_matches);
- $results->type = $array_matches[1];
- $results->array = isset($array_matches[2]);
- // set the cached value
- self::$cache[$property->getDeclaringClass()->getName()][$property->getName()] = $results;
- }
- return self::$cache[$property->getDeclaringClass()->getName()][$property->getName()];
- }
- /**
- * @param $errno
- * @param $errstr
- * @param $errfile
- * @param $errline
- *
- * @throws ErrorException
- */
- public function exception_error_handler($errno, $errstr, $errfile, $errline)
- {
- throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
- }
- }