Skip to content

Commit

Permalink
Merge pull request #24 from prezly/feature/versionable-serialization
Browse files Browse the repository at this point in the history
Feature - Versionable serialization
  • Loading branch information
e1himself committed Jun 20, 2019
2 parents f326b00 + bd30e87 commit cce06f5
Show file tree
Hide file tree
Showing 54 changed files with 2,322 additions and 705 deletions.
12 changes: 0 additions & 12 deletions src/Model/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,4 @@ public function getText(): string
}
return $text;
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::BLOCK,
'type' => $this->type,
'data' => (object) $this->data,
'nodes' => array_map(function (Entity $node) {
return $node->jsonSerialize();
}, $this->nodes)
];
}
}
11 changes: 0 additions & 11 deletions src/Model/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,4 @@ public function getText(): string
}
return $text;
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::DOCUMENT,
'data' => (object) $this->data,
'nodes' => array_map(function (Entity $node) {
return $node->jsonSerialize();
}, $this->nodes),
];
}
}
2 changes: 1 addition & 1 deletion src/Model/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Prezly\Slate\Model;

interface Entity extends \JsonSerializable
interface Entity
{
const BLOCK = "block";
const DOCUMENT = "document";
Expand Down
12 changes: 0 additions & 12 deletions src/Model/Inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,4 @@ public function getText(): string
}
return $text;
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::INLINE,
'type' => $this->type,
'data' => (object) $this->data,
'nodes' => array_map(function (Entity $node) {
return $node->jsonSerialize();
}, $this->nodes)
];
}
}
11 changes: 0 additions & 11 deletions src/Model/Leaf.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,4 @@ public function withMarks(array $marks): Leaf
{
return new self($this->text, $marks);
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::LEAF,
'text' => $this->text,
'marks' => array_map(function (Mark $mark) {
return $mark->jsonSerialize();
}, $this->marks)
];
}
}
9 changes: 0 additions & 9 deletions src/Model/Mark.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,4 @@ public function withData(array $data): Mark
{
return new self($this->type, $data);
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::MARK,
'type' => $this->type,
'data' => (object) $this->data,
];
}
}
10 changes: 0 additions & 10 deletions src/Model/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,4 @@ public function getText(): string
}
return $text;
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::TEXT,
'leaves' => array_map(function (Leaf $leaf) {
return $leaf->jsonSerialize();
}, $this->leaves)
];
}
}
13 changes: 4 additions & 9 deletions src/Model/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Prezly\Slate\Model;

use Prezly\Slate\Serialization\Serializer;

class Value implements Entity
{
/** @var Document */
Expand Down Expand Up @@ -29,16 +31,9 @@ public function withDocument(Document $document): Value
return new self($document);
}

public function jsonSerialize()
{
return (object) [
'object' => Entity::VALUE,
'document' => $this->document->jsonSerialize(),
];
}

public function toJson(int $options = 0): string
{
return json_encode($this->jsonSerialize(), $options);
$serializer = new Serializer(Serializer::LATEST_SERIALIZATION_VERSION, $options);
return $serializer->toJson($this);
}
}
45 changes: 45 additions & 0 deletions src/Serialization/DefaultVersionSerializerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
namespace Prezly\Slate\Serialization;

use Prezly\Slate\Serialization\Exceptions\UnsupprotedVersionException;
use Prezly\Slate\Serialization\Versions\v0_40_VersionSerializer;
use Prezly\Slate\Serialization\Versions\v0_46_VersionSerializer;
use Prezly\Slate\Serialization\Versions\VersionSerializer;

class DefaultVersionSerializerFactory implements VersionSerializerFactory
{
private const SERIALIZATION_VERSIONS = [
'0.40' => v0_40_VersionSerializer::class,
'0.41' => v0_40_VersionSerializer::class,
'0.42' => v0_40_VersionSerializer::class,
'0.43' => v0_40_VersionSerializer::class,
'0.44' => v0_40_VersionSerializer::class,
'0.45' => v0_40_VersionSerializer::class,
// 0.46 - leaves data combined into text nodes
'0.46' => v0_46_VersionSerializer::class,
'0.47' => v0_46_VersionSerializer::class,
];

/** @var array */
private $serialization_versions;

public function __construct(array $serialization_versions = null)
{
$this->serialization_versions = $serialization_versions ?? self::SERIALIZATION_VERSIONS;
}

public function getSerializer(string $version): VersionSerializer
{
$generic_version = implode('.', array_slice(explode('.', $version), 0, 2));

if (! isset($this->serialization_versions[$generic_version])) {
throw new UnsupprotedVersionException($version);
}

$serializer_class = $this->serialization_versions[$generic_version];
/** @var \Prezly\Slate\Serialization\Versions\VersionSerializer $serializer */
$serializer = new $serializer_class();

return $serializer;
}
}
14 changes: 14 additions & 0 deletions src/Serialization/Exceptions/UnsupprotedVersionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
namespace Prezly\Slate\Serialization\Exceptions;

use InvalidArgumentException;

class UnsupprotedVersionException extends InvalidArgumentException
{
public function __construct(string $version)
{
$message = "Unsupported serialization version requested: {$version}";

parent::__construct($message, 0, null);
}
}
92 changes: 92 additions & 0 deletions src/Serialization/Serializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
namespace Prezly\Slate\Serialization;

use Prezly\Slate\Model\Entity;
use Prezly\Slate\Model\Value;
use Prezly\Slate\Serialization\Support\ShapeValidator;
use stdClass;

class Serializer implements ValueSerializer
{
public const LATEST_SERIALIZATION_VERSION = '0.47';

/** @var string */
private $default_version;

/** @var int */
private $json_encode_options;

/** @var \Prezly\Slate\Serialization\VersionSerializerFactory */
private $factory;

/**
* @param string|null $default_version Default serialization version to use
* when serializing/unserializing with no version set.
* @param int|null $json_encode_options JSON options to use for json_encode().
* @param VersionSerializerFactory|null Factory used to get VersionSerializer for a given factory.
*/
public function __construct(
?string $default_version = self::LATEST_SERIALIZATION_VERSION,
int $json_encode_options = null,
?VersionSerializerFactory $factory = null
) {
$this->default_version = $default_version ?? self::LATEST_SERIALIZATION_VERSION;
$this->json_encode_options = $json_encode_options;
$this->factory = $factory ?? new DefaultVersionSerializerFactory();
}

/**
* Serialize value to JSON
*
* Optionally you can provide desired serialization version.
*
* If no version argument provided, default serialization version
* will be used (which is set to LATEST by default).
*
* @param \Prezly\Slate\Model\Value $value
* @param string|null $version
* @return string
*/
public function toJson(Value $value, ?string $version = null): string
{
return json_encode(
$this->serializeValue($value, $version),
$this->json_encode_options
);
}

/**
* Unserialize value from JSON
*
* Optional you can provide serialization version to use
* in case if value JSON does not have "version" property.
*
* If no version argument is given, default serialization
* version will be implied (which is set to LATEST by default).
*
* @param string $value
* @param string|null $default_version
* @return \Prezly\Slate\Model\Value
*/
public function fromJson(string $value, ?string $default_version = null): Value
{
return $this->unserializeValue(json_decode($value, false));
}

private function serializeValue(Value $value, ?string $version): stdClass
{
$version = $version ?? $this->default_version;
$object = $this->factory->getSerializer($version)->serializeValue($value);
$object->version = $version;

return $object;
}

private function unserializeValue($value, ?string $default_version = null): Value
{
$object = ShapeValidator::validateSlateObject($value, Entity::VALUE);
$version = $object->version ?? $default_version ?? $this->default_version;

return $this->factory->getSerializer($version)->unserializeValue($object);
}
}
71 changes: 71 additions & 0 deletions src/Serialization/Support/ShapeValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
namespace Prezly\Slate\Serialization\Support;

use InvalidArgumentException;
use stdClass;

/**
* @internal Please do not use this class outside of this package.
* It's considered internal API and thus is not a subject for semantic versioning.
* The interface may change in future without major version bump.
*/
class ShapeValidator
{
/**
* @param \stdClass|mixed $object
* @param string|null $object_type
* @param callable[] $shape [ string $property_name => string $check_function, ... ]
* @return \stdClass
* @throws \InvalidArgumentException
*/
public static function validateSlateObject($object, string $object_type = null, array $shape = []): stdClass
{
// Validate it's an stdClass
if (! $object instanceof stdClass) {
throw new InvalidArgumentException(sprintf(
'Unexpected JSON value given: %s. An object is expected to construct %s.',
gettype($object),
ucfirst($object_type) ?: 'a Slate structure object'
));
}

// Validate "object" property presence
if (! property_exists($object, 'object')) {
throw new InvalidArgumentException(sprintf(
'Invalid JSON structure given to construct %s. It should have "object" property.',
ucfirst($object_type)
));
}

// Validate "object" property value
if ($object_type !== null && $object_type !== $object->object) {
throw new InvalidArgumentException(sprintf(
'Invalid JSON structure given to construct %s. It should have "object" property set to "%s".',
ucfirst($object_type),
$object_type
));
}

// Validate Shape
foreach ($shape as $property => $checker) {
if (! property_exists($object, $property)) {
throw new InvalidArgumentException(sprintf(
'Unexpected JSON structure given for %s. A %s should have "%s" property.',
ucfirst($object_type),
ucfirst($object_type),
$property
));
}
if (! $checker($object->$property)) {
throw new InvalidArgumentException(sprintf(
'Unexpected JSON structure given for %s. The "%s" property should be %s.',
ucfirst($object_type),
$property,
substr($checker, 0, 3) === 'is_' ? substr($checker, 3) : $checker
));
}
}

return $object;
}
}
11 changes: 11 additions & 0 deletions src/Serialization/ValueSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
namespace Prezly\Slate\Serialization;

use Prezly\Slate\Model\Value;

interface ValueSerializer
{
public function toJson(Value $value, ?string $version = null): string;

public function fromJson(string $value, ?string $default_version = null): Value;
}
9 changes: 9 additions & 0 deletions src/Serialization/VersionSerializerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
namespace Prezly\Slate\Serialization;

use Prezly\Slate\Serialization\Versions\VersionSerializer;

interface VersionSerializerFactory
{
public function getSerializer(string $version): VersionSerializer;
}
Loading

0 comments on commit cce06f5

Please sign in to comment.