From f1d00615b6d54d617c67fa6404ed43bca274da1a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 3 Dec 2022 02:19:49 +0100 Subject: [PATCH] Add @type phpdoc to allow for typed arrays --- src/SeriousJSON/JsonDatabase.php | 14 ++-- src/SeriousJSON/JsonSerializable.php | 99 ++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/SeriousJSON/JsonDatabase.php b/src/SeriousJSON/JsonDatabase.php index be20fd7..a260c21 100644 --- a/src/SeriousJSON/JsonDatabase.php +++ b/src/SeriousJSON/JsonDatabase.php @@ -189,22 +189,22 @@ class JsonDatabase implements IJsonUnflattener return $name; } - public function delete(string $className, $identifier): bool + public function delete(string $className, $identifier, ?int $timestamp = null): bool { - if ($class == null || empty(trim($name))) + if ($className == null || empty(trim($identifier))) return false; - if ($name != null && !empty(trim(strval($name)))) - $name = JSONDatabase::SanitizeIdentifier($name); + if ($identifier != null && !empty(trim(strval($identifier)))) + $identifier = JSONDatabase::SanitizeIdentifier($identifier); else return false; - $class = trim($class); + $class = trim($className); if (!$this->audit && $timestamp != null) return false; - $objPath = $this->getObjectPath($class, $name, ($timestamp != null)); + $objPath = $this->getObjectPath($className, $identifier, ($timestamp != null)); if (strlen($objPath) > PHP_MAXPATHLEN) return false; @@ -215,7 +215,7 @@ class JsonDatabase implements IJsonUnflattener return unlink($objPath); } - public function delete(IJsonIdentifiable $obj): bool + public function deleteObj(IJsonIdentifiable $obj): bool { return $this->delete(get_class($obj), $obj->flatIdentifier()); } diff --git a/src/SeriousJSON/JsonSerializable.php b/src/SeriousJSON/JsonSerializable.php index dec7ecc..41e6e60 100644 --- a/src/SeriousJSON/JsonSerializable.php +++ b/src/SeriousJSON/JsonSerializable.php @@ -37,6 +37,52 @@ abstract class JsonSerializable return (new \ReflectionClass($typeName))->isSubclassOf(JsonSerializable::class); } + /** + * Check if is strongly typed array of type JsonSerializable and return the type + * @param string $property Property we should check against + * @return bool returns class name if type/class of property extends JsonSerializable + */ + private static function getPropertyArrayType(\ReflectionProperty $property) + { + if (!isset($property) || $property === null || !$property->hasType()) + return false; + + // Get builtin type or class of given property + $type = $property->getType(); + + // A built-in type is any type that is not a class, interface, or trait. + // Assume false on simple types, we can only check this on classes + if (!$type->isBuiltin() && $type->getName() != 'array') + return null; + + $docComment = $property->getDocComment(); + + if (!$docComment) + return null; + + $line = strtok($docComment, "\r\n"); + + // TODO: Maybe use preg_match here, maybe faster: https://stackoverflow.com/a/6250269 + while ($line !== false) { + $docType = substr($line, strpos($line, '@type ') + 6); + if (!empty(trim($docType))) + { + $docType = trim($docType); + if (class_exists($docType)) { + $refClass = new \ReflectionClass($docType); + if ($refClass->isSubclassOf(JsonSerializable::class)) + return $refClass->getName(); + } + // We found a type property, do not further iterate + return null; + } + + $line = strtok( "\r\n" ); + } + + return null; + } + /** * Serialize a JsonSerializable Object to an array or to a json string * @param bool $nested Indicates if we should also serialize nested object instances @@ -68,6 +114,13 @@ abstract class JsonSerializable continue; } + else if (isset($value) && $value != null && is_array($value)) { + $arrayType = self::getPropertyArrayType(new \ReflectionProperty($this, $key)); + if ($arrayType != null) + $result[$key] = self::SerializeArray($value, $nested, true); + else + $result[$key] = $value; + } else { $result[$key] = $value; } @@ -86,17 +139,29 @@ abstract class JsonSerializable * @param bool $nested Indicates if we should also serialize nested object instances * @return string returns a json formatted string */ - public static function SerializeArray($objects = [], $nested = false) + public static function SerializeArray($objects = [], $nested = false, $to_array = false) { // Initialize a an array of results $results = array(); // Iterate through provided JsonSerializable Objects and serialize them - foreach ($objects as $object) - $results[] = $object->Serialize($nested, true); + foreach ($objects as $object) { + if ($object instanceof JsonSerializable) + { + if ($nested) + $results[] = $object->Serialize(nested: true, to_array: true); + else if ($object instanceof IJsonIdentifiable && $object->flatIdentifier() !== null) + $results[] = $object->flatIdentifier(); + else + continue; + } + } - // Return the json encoded string - return json_encode($results); + // If json is demanded, encode, else just return the array + if ($to_array) + return $results; + else + return json_encode($results); } /** @@ -135,7 +200,15 @@ abstract class JsonSerializable // If simple data type, just assign it to var in class // TODO: Exception handling, what if class specifies an int but string was provided? $selfSubclass = self::isPropertyJsonSerializable($property); - if ($selfSubclass) + $arrayType = self::getPropertyArrayType($property); + if ($arrayType != null) + { + if (is_array($value)) + $classInstance->{$key} = self::DeserializeArray($value, $nested, $callback, $arrayType); + else + continue; + } + elseif ($selfSubclass) { try { if (is_array($value)) { @@ -183,7 +256,7 @@ abstract class JsonSerializable * @param string|array $json json string containing the array or array to decode * @return JsonSerializable[] Returns an Array of Instances of JsonSerializable */ - public static function DeserializeArray(string|array $json, bool $nested = false) + public static function DeserializeArray(string|array $json, bool $nested = false, IJsonUnflattener $callback = null, string $className = null) { // Check if we need to decode from json first if (is_string($json)) @@ -194,14 +267,12 @@ abstract class JsonSerializable // Loop through the array foreach ($json as $item) { - // Uh oh, check if there is an nested array within the array and deserialize children - // If not, or if we are not supposed to, just deserialize the current layer or ignore - if ($nested && is_array($item)) - $items[] = self::DeserializeArray(json: $item, nested: $nested); - else if (is_array($item)) - continue; + if (is_array($item)) + $items[] = self::Deserialize(json: $item, nested: $nested, callback: $callback, className: $className); + else if ($className != null && $callback != null && !is_object($item)) + $items[] = call_user_func([$callback, 'unflattenByID'], $className, $item); else - $items[] = self::Deserialize(json: $item, nested: $nested); + continue; } // Returns the result