<?php
/**
* Serializer
*
* @author Bertin van den Ham <b.vandenham@visualmedia.nl>
* @author Vincent van Waasbergen <v.vanwaasbergen@visualmedia.nl>
*/
namespace VisualMedia\LisaBundle\Component;
use DateTime;
use ReflectionClass;
use ReflectionMethod;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use VisualMedia\LisaBundle\Component\Interfaces\SingletonInterface;
use VisualMedia\LisaBundle\Helper\EntityHelper;
use VisualMedia\LisaBundle\Helper\Helper;
use VisualMedia\LisaBundle\Component\Interfaces\EntityInterface;
/**
* Serializer
*/
abstract class Serializer implements SerializerInterface
{
/**
* @var string
*/
protected $class;
/**
* @var EntityManagerInterface
*/
protected $em;
/**
* @var array
*/
public $registry = array();
/**
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* {@inheritdoc}
*/
public function serialize($model): string
{
if ($model !== null) {
$this->class = get_class($model);
}
return $this->encode($this->normalize($model));
}
/**
* {@inheritdoc}
*/
public function deserialize(?string $json)
{
return $this->denormalize($this->decode($json));
}
/**
* {@inheritdoc}
*/
public function encode(array $data): string
{
return json_encode($data, JSON_PRETTY_PRINT);
}
/**
* {@inheritdoc}
*/
public function decode(?string $json): array
{
return json_decode($json, true) ?: [];
}
/**
* {@inheritdoc}
*/
public function normalize($model): array
{
if ($model !== null) {
$this->class = get_class($model);
}
$this->registry = array();
return $this->normalizeInternal($model);
}
/**
* {@inheritdoc}
*/
public function normalizeInternal($model): array
{
if ($model === null) {
return array();
}
if ($model instanceof EntityInterface && get_class($model) !== $this->class && $model->getId() !== null) {
return $this->normalizeValue($model);
}
$data = array(
static::META_INSTANCE => sprintf('%s%s%s', get_class($model), static::META_INSTANCE_DELIMITER, spl_object_id($model)),
);
$this->registry[spl_object_id($model)] = $model;
$reflection = new ReflectionClass($model);
foreach ($reflection->getProperties() as $reflectionProperty) {
// Property name.
if (null === $name = $reflectionProperty->name) {
continue;
}
// Get data.
$getter = EntityHelper::getter($name);
if (method_exists($model, $getter)) {
$value = $model->{$getter}();
}
elseif (property_exists($model, $name) && $reflectionProperty->isPublic()) {
$value = $model->{$name};
}
else {
continue;
}
// Array.
if ($value instanceof Collection) {
$data[$name][static::META_COLLECTION] = true;
foreach ($value as $k=>$v) {
$data[$name][$k] = $this->normalizeValue($v);
}
}
elseif (is_array($value)) {
foreach ($value as $k=>$v) {
$data[$name][$k] = $this->normalizeValue($v);
}
}
else {
$data[$name] = $this->normalizeValue($value);
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function normalizeValue($value)
{
// Native.
if (is_string($value) || is_bool($value) || is_numeric($value)) {
return $value;
}
// DateTime.
if ($value instanceof DateTime) {
return $value->format('Y-m-d H:i:s');
}
// Object.
if (is_object($value)) {
return $this->normalizeOnce($value);
}
return null;
}
/**
* {@inheritdoc}
*/
public function normalizeOnce($model): array
{
// Entity.
if ($model instanceof EntityInterface && $model->getId() !== null) {
$data = array(static::META_DATABASE => sprintf('%s%s%s', get_class($model), static::META_DATABASE_DELIMITER, $model->getId()));
}
// New instance.
elseif (null === $instance = $this->registry[$id = spl_object_id($model)] ?? null) {
$this->registry[$id] = $model;
$data = $this->normalizeInternal($model);
}
// Instance reference.
else {
$data = array(static::META_INSTANCE => sprintf('%s%s%s', get_class($model), static::META_INSTANCE_DELIMITER, $id));
}
return $data;
}
/**
* {@inheritdoc}
*/
public function denormalize(array $data, string $class = null)
{
$this->registry = array();
return $this->denormalizeInternal($data, $class);
}
/**
* {@inheritdoc}
*/
public function denormalizeInternal(array $data, string $class = null)
{
if (null !== $database = $data[static::META_DATABASE] ?? null) {
return $this->denormalizeValue($data);
}
@list($instanceClass, $id) = explode(static::META_INSTANCE_DELIMITER, $data[static::META_INSTANCE]);
if (null === $class = $instanceClass ?: $class) {
return null;
}
$reflection = new ReflectionClass($class);
if ($reflection->implementsInterface(SingletonInterface::class)) {
$model = $class::getSingleInstance();
}
else {
$model = new $class();
}
$this->registry[$id] = $model;
foreach ($data as $key=>$value) {
$setter = EntityHelper::setter($key);
// Object.
if (null !== $id = $value[static::META_INSTANCE] ?? null) {
$value = $this->denormalizeOnce($value);
}
// Array.
elseif (is_array($value)) {
foreach ($value as $k=>$v) {
if (null !== $id = $v[static::META_INSTANCE] ?? null) {
$value[$k] = $this->denormalizeOnce($v);
}
}
}
// Value.
if (null === $value = $this->denormalizeValue($value)) {
continue;
}
// Set data.
if (method_exists($model, $setter)) {
$model->{$setter}($value);
}
elseif (property_exists($model, $key) && $reflection->getProperty($key)->isPublic()) {
$model->{$key} = $value;
}
}
return $model;
}
/**
* {@inheritdoc}
*/
public function denormalizeValue($value)
{
// Empty.
if ($value === '') {
return null;
}
// Numeric.
if (is_numeric($value)) {
if ((int)$value !== $value) {
$value = (float)$value;
}
else {
$value = (int)$value;
}
}
// DateTime.
if (is_string($value) && preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $value)) {
$value = new DateTime($value);
}
// Collection.
if (is_array($value) && true === $collection = $value[static::META_COLLECTION] ?? null) {
$collection = new ArrayCollection();
foreach ($value as $k=>$v) {
if ($k === static::META_COLLECTION) {
continue;
}
$collection->add($this->denormalizeValue($v));
}
$value = $collection;
}
// Database.
if (is_array($value) && null !== $key = $value[static::META_DATABASE] ?? null) {
@list($class, $id) = explode(static::META_DATABASE_DELIMITER, $key);
$repository = $this->em->getRepository($class);
$value = $repository->getFirst(new ManagerData(array($repository::OPTION_ID => $id)));
}
return $value;
}
/**
* {@inheritdoc}
*/
public function denormalizeOnce(array $data)
{
@list($class, $id) = explode(static::META_INSTANCE_DELIMITER, $data[static::META_INSTANCE]);
// New instance.
if (null === $instance = $this->registry[$id] ?? null) {
$model = $this->denormalizeInternal($data);
$this->registry[$id] = $model;
}
// Instance reference.
else {
$model = $this->registry[$id];
}
return $model;
}
}