Skip to content

Commit

Permalink
Adds traits with a generalized json serializer and hydration solution…
Browse files Browse the repository at this point in the history
…s. (#13)
  • Loading branch information
fmizzell authored Nov 7, 2019
1 parent aee842b commit baabdf6
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 85 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
}
],
"require": {
"getdkan/contracts": "^1.0.0"
"getdkan/contracts": "^1.0.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7.5"
Expand Down
71 changes: 58 additions & 13 deletions src/HydratableTrait.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,71 @@
<?php


namespace Procrastinator;

/**
* @todo Change name to HydratableTrait.
*/
trait HydratableTrait
{
public static function hydrate(string $json, $instance = null)
{
$data = json_decode($json);
$data = (array) json_decode($json);

$class = new \ReflectionClass(static::class);

if (!$instance) {
$instance = $class->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);
}
}
17 changes: 10 additions & 7 deletions src/Job/AbstractPersistentJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/Job/Job.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

namespace Procrastinator\Job;

use Procrastinator\JsonSerializeTrait;
use Procrastinator\Result;

/**
* @todo Change name to AbstractJob.
*/
abstract class Job implements \JsonSerializable
{
use JsonSerializeTrait;

private $result;
private $timeLimit = PHP_INT_MAX;

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions src/Job/Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

namespace Procrastinator\Job;

use Procrastinator\Result;

class Method extends Job
{
private $object;
Expand Down
70 changes: 70 additions & 0 deletions src/JsonSerializeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Procrastinator;

trait JsonSerializeTrait
{
private function serialize()
{
$serialized = [];

$properties = [];
$class = new \ReflectionClass(static::class);
$parent = $class;
while ($parent) {
$properties = array_merge($properties, $parent->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 [];
}
}
2 changes: 1 addition & 1 deletion test/Job/AbstractPersistentJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
2 changes: 1 addition & 1 deletion test/Job/JobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
55 changes: 0 additions & 55 deletions test/Job/Mock/Persistor.php

This file was deleted.

34 changes: 34 additions & 0 deletions test/Mock/Complex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace ProcrastinatorTest\Mock;

use Contracts\HydratableInterface;
use Procrastinator\HydratableTrait;
use Procrastinator\JsonSerializeTrait;

class Complex implements \JsonSerializable, HydratableInterface
{
use JsonSerializeTrait;
use HydratableTrait;

private $stuff;

public function __construct()
{
$this->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();
}
}
32 changes: 32 additions & 0 deletions test/Mock/Persistor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php


namespace ProcrastinatorTest\Mock;

use Procrastinator\Job\AbstractPersistentJob;

class Persistor extends AbstractPersistentJob
{
private $errorOut = false;

public function errorOut()
{
$this->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;
}
}
2 changes: 1 addition & 1 deletion test/Job/Mock/TwoStage.php → test/Mock/TwoStage.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace ProcrastinatorTest\Job\Mock;
namespace ProcrastinatorTest\Mock;

use Procrastinator\Job\Job;
use Procrastinator\Result;
Expand Down
Loading

0 comments on commit baabdf6

Please sign in to comment.