From baabdf6e21c447efa44186e2be216901719f8928 Mon Sep 17 00:00:00 2001 From: fmizzell Date: Thu, 7 Nov 2019 10:32:08 -0600 Subject: [PATCH] Adds traits with a generalized json serializer and hydration solutions. (#13) --- composer.json | 3 +- src/HydratableTrait.php | 71 +++++++++++++++++++++----- src/Job/AbstractPersistentJob.php | 17 +++--- src/Job/Job.php | 8 +-- src/Job/Method.php | 2 - src/JsonSerializeTrait.php | 70 +++++++++++++++++++++++++ test/Job/AbstractPersistentJobTest.php | 2 +- test/Job/JobTest.php | 2 +- test/Job/Mock/Persistor.php | 55 -------------------- test/Mock/Complex.php | 34 ++++++++++++ test/Mock/Persistor.php | 32 ++++++++++++ test/{Job => }/Mock/TwoStage.php | 2 +- test/SerializeHydrateTest.php | 23 +++++++++ 13 files changed, 236 insertions(+), 85 deletions(-) create mode 100644 src/JsonSerializeTrait.php delete mode 100644 test/Job/Mock/Persistor.php create mode 100644 test/Mock/Complex.php create mode 100644 test/Mock/Persistor.php rename test/{Job => }/Mock/TwoStage.php (94%) create mode 100644 test/SerializeHydrateTest.php diff --git a/composer.json b/composer.json index cfdc875..4d3e0b6 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "getdkan/contracts": "^1.0.0" + "getdkan/contracts": "^1.0.0", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^7.5" diff --git a/src/HydratableTrait.php b/src/HydratableTrait.php index 391428e..4deb003 100644 --- a/src/HydratableTrait.php +++ b/src/HydratableTrait.php @@ -1,26 +1,71 @@ newInstanceWithoutConstructor(); + } - $reflector = new \ReflectionClass(self::class); - $object = $reflector->newInstanceWithoutConstructor(); + $properties = []; + $parent = $class; + while ($parent) { + $properties = array_merge($properties, $parent->getProperties()); + $parent = $parent->getParentClass(); + } - $reflector = new \ReflectionClass($object); - foreach ($data as $property => $value) { - $p = $reflector->getProperty($property); - $p->setAccessible(true); - $p->setValue($object, $value); + /* @var $property \ReflectionProperty */ + foreach ($properties as $property) { + $name = $property->getName(); + if (isset($data[$name])) { + $property->setAccessible(true); + $property->setValue($instance, static::hydrateProcessValue($data[$name])); + } } - return $object; + return $instance; + } + + private static function hydrateProcessValue($value) + { + if (is_object($value)) { + $value = (array) $value; + if (isset($value["@type"])) { + $type = $value["@type"]; + $value = (object) $value; + switch ($type) { + case "object": + return static::hydrateProcessValueObject($value); + case "array": + return static::hydrateProcessValueArray($value); + } + throw new \Exception("Unrecognized type: {$type}."); + } else { + return (object) $value; + } + } + return $value; + } + + private static function hydrateProcessValueObject($value) + { + $value = (array) $value; + $class = $value['@class']; + return $class::hydrate(json_encode($value['data'])); + } + + private static function hydrateProcessValueArray($value) + { + $value = (array) $value; + $array = (array) $value['data']; + return array_map(function ($item) { + return static::hydrateProcessValue($item); + }, $array); } } diff --git a/src/Job/AbstractPersistentJob.php b/src/Job/AbstractPersistentJob.php index 75e1ed1..9e85144 100644 --- a/src/Job/AbstractPersistentJob.php +++ b/src/Job/AbstractPersistentJob.php @@ -5,10 +5,13 @@ use Contracts\StorerInterface; use Contracts\RetrieverInterface; use Contracts\HydratableInterface; +use Procrastinator\HydratableTrait; use Procrastinator\Result; abstract class AbstractPersistentJob extends Job implements HydratableInterface { + use HydratableTrait; + private $identifier; private $storage; @@ -48,13 +51,6 @@ public function setTimeLimit(int $seconds): bool return $return; } - public function jsonSerialize() - { - $object = parent::jsonSerialize(); - $object->identifier = $this->identifier; - return $object; - } - protected function setStatus($status) { parent::setStatus($status); @@ -73,6 +69,13 @@ protected function setState($state) $this->selfStore(); } + protected function serializeIgnoreProperties(): array + { + $ignore = parent::serializeIgnoreProperties(); + $ignore[] = "storage"; + return $ignore; + } + private function selfStore() { $this->storage->store(json_encode($this), $this->identifier); diff --git a/src/Job/Job.php b/src/Job/Job.php index 47b94ef..5e665e6 100644 --- a/src/Job/Job.php +++ b/src/Job/Job.php @@ -3,6 +3,7 @@ namespace Procrastinator\Job; +use Procrastinator\JsonSerializeTrait; use Procrastinator\Result; /** @@ -10,6 +11,8 @@ */ abstract class Job implements \JsonSerializable { + use JsonSerializeTrait; + private $result; private $timeLimit = PHP_INT_MAX; @@ -105,10 +108,7 @@ public function getResult(): Result public function jsonSerialize() { - return (object) [ - 'timeLimit' => $this->timeLimit, - 'result' => $this->getResult()->jsonSerialize() - ]; + return $this->serialize(); } protected function setStatus($status) diff --git a/src/Job/Method.php b/src/Job/Method.php index caf7381..ee55bb2 100644 --- a/src/Job/Method.php +++ b/src/Job/Method.php @@ -3,8 +3,6 @@ namespace Procrastinator\Job; -use Procrastinator\Result; - class Method extends Job { private $object; diff --git a/src/JsonSerializeTrait.php b/src/JsonSerializeTrait.php new file mode 100644 index 0000000..119cd2f --- /dev/null +++ b/src/JsonSerializeTrait.php @@ -0,0 +1,70 @@ +getProperties()); + $parent = $parent->getParentClass(); + } + + /* @var $property \ReflectionProperty */ + foreach ($properties as $property) { + $name = $property->getName(); + if (!in_array($name, $this->serializeIgnoreProperties())) { + $property->setAccessible(true); + $serialized[$property->getName()] = $this->serializeProcessValue($property->getValue($this)); + } + } + + return $serialized; + } + + private function serializeProcessValue($value) + { + if (is_object($value)) { + return $this->serializeProcessValueObject($value); + } elseif (is_array($value)) { + return $this->serializeProcessValueArray($value); + } + return $value; + } + + private function serializeProcessValueObject($object) + { + if ($object instanceof \stdClass) { + return $object; + } elseif ($object instanceof \JsonSerializable) { + return ['@type' => 'object', '@class' => get_class($object), 'data' => $object->jsonSerialize()]; + } else { + $class = get_class($object); + $interface = \JsonSerializable::class; + $message = "Failed to serialize {$class} object as it does not implement {$interface}"; + throw new \Exception($message); + } + } + + private function serializeProcessValueArray($array) + { + $serialized = []; + + foreach ($array as $key => $value) { + $serialized[$key] = $this->serializeProcessValue($value); + } + + return ['@type' => 'array', 'data' => $serialized]; + } + + protected function serializeIgnoreProperties(): array + { + return []; + } +} diff --git a/test/Job/AbstractPersistentJobTest.php b/test/Job/AbstractPersistentJobTest.php index 2287122..2cb43fd 100644 --- a/test/Job/AbstractPersistentJobTest.php +++ b/test/Job/AbstractPersistentJobTest.php @@ -4,7 +4,7 @@ use Contracts\Mock\Storage\Memory; use PHPUnit\Framework\TestCase; -use ProcrastinatorTest\Job\Mock\Persistor; +use ProcrastinatorTest\Mock\Persistor; class AbstractPersistentJobTest extends TestCase { diff --git a/test/Job/JobTest.php b/test/Job/JobTest.php index 4a97015..0cbbf2d 100644 --- a/test/Job/JobTest.php +++ b/test/Job/JobTest.php @@ -5,7 +5,7 @@ use PHPUnit\Framework\TestCase; use Procrastinator\Job\Method; use Procrastinator\Result; -use ProcrastinatorTest\Job\Mock\TwoStage; +use ProcrastinatorTest\Mock\TwoStage; class JobTest extends TestCase { diff --git a/test/Job/Mock/Persistor.php b/test/Job/Mock/Persistor.php deleted file mode 100644 index d95e618..0000000 --- a/test/Job/Mock/Persistor.php +++ /dev/null @@ -1,55 +0,0 @@ -newInstanceWithoutConstructor(); - } - - $object = json_decode($json); - - $job = $class->getParentClass()->getParentClass(); - - $result = $job->getProperty("result"); - $result->setAccessible(true); - $result->setValue($instance, Result::hydrate(json_encode($object->result))); - - $timeLimit = $job->getProperty("timeLimit"); - $timeLimit->setAccessible(true); - $timeLimit->setValue($instance, $object->timeLimit); - - $persistentJob = $class->getParentClass(); - - $identifier = $persistentJob->getProperty("identifier"); - $identifier->setAccessible(true); - $identifier->setValue($instance, $object->identifier); - - return $instance; - } - - public function errorOut() - { - $this->errorOut = true; - } - - protected function runIt() - { - if ($this->errorOut) { - throw new \Exception("ERROR"); - } - $this->setStateProperty("ran", true); - return; - } -} diff --git a/test/Mock/Complex.php b/test/Mock/Complex.php new file mode 100644 index 0000000..36005e1 --- /dev/null +++ b/test/Mock/Complex.php @@ -0,0 +1,34 @@ +stuff["hello"] = (object) [ + "first_name" => "Gerardo", + "last_name" => "Gonzalez" + ]; + $this->stuff['goodbye'] = 2; + } + + public function getItem($key) + { + return $this->stuff[$key]; + } + + public function jsonSerialize() + { + return $this->serialize(); + } +} diff --git a/test/Mock/Persistor.php b/test/Mock/Persistor.php new file mode 100644 index 0000000..3b86280 --- /dev/null +++ b/test/Mock/Persistor.php @@ -0,0 +1,32 @@ +errorOut = true; + } + + protected function runIt() + { + if ($this->errorOut) { + throw new \Exception("ERROR"); + } + $this->setStateProperty("ran", true); + return; + } + + protected function serializeIgnoreProperties(): array + { + $properties = parent::serializeIgnoreProperties(); + $properties[] = 'errorOut'; + return $properties; + } +} diff --git a/test/Job/Mock/TwoStage.php b/test/Mock/TwoStage.php similarity index 94% rename from test/Job/Mock/TwoStage.php rename to test/Mock/TwoStage.php index 554a227..7fb1cab 100644 --- a/test/Job/Mock/TwoStage.php +++ b/test/Mock/TwoStage.php @@ -1,6 +1,6 @@ getItem('hello'); + $this->assertTrue(is_object($hello)); + $this->assertEquals('Gerardo', $hello->first_name); + $this->assertEquals('Gonzalez', $hello->last_name); + $this->assertEquals(2, $object2->getItem("goodbye")); + } +}