Permalink
Browse files

Add support for datatype normalizers (DateTime normalizer included)

This adds support to convert typed values to PHP objects and vice-versa. A first normalizer for DateTime objects respectively XSD dateTime values has been added.
  • Loading branch information...
lanthaler committed Nov 23, 2012
1 parent fc9313e commit 1565ba2eb8a7f25cd2286c2e06910d966029c917
@@ -0,0 +1,143 @@
<?php
/*
* (c) Markus Lanthaler <mail@markus-lanthaler.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ML\HydraBundle\DatatypeNormalizer;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use ML\JsonLD\TypedValue;
/**
* Converts XSD dateTime values into PHP DateTime objects and vice-versa
*
* @author Markus Lanthaler <mail@markus-lanthaler.com>
*/
class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface
{
/**
* The supported format
*/
const FORMAT = 'jsonld';
/**
* The XML Schema dateTime datatype IRI
*/
const XSD_DATETIME_IRI = 'http://www.w3.org/2001/XMLSchema#dateTime';
/**
* The canonical XSD dateTime format (all times have to be in UTC)
*/
const XSD_DATETIME_FORMAT = 'Y-m-d\TH:i:s\Z';
/**
* Returns the IRI identifying the (data)type
*
* @return string The IRI identifying the (data)type
*/
public function getTypeIri()
{
return self::XSD_DATETIME_IRI;
}
/**
* Normalizes an object into a set of arrays/scalars
*
* @param object $object object to normalize
* @param string $format format the normalization result will be encoded as
*
* @return string
*/
public function normalize($object, $format = null)
{
$dt = clone $object;
$dt->setTimezone(new \DateTimeZone('UTC'));
return $object->format(self::XSD_DATETIME_FORMAT);
}
/**
* Checks whether the given class is supported for normalization by this normalizer
*
* @param mixed $data Data to normalize.
* @param string $format The format being (de-)serialized from or into.
*
* @return Boolean
*/
public function supportsNormalization($data, $format = null)
{
return is_object($data) && ($data instanceof \DateTime) && (self::FORMAT === $format);
}
/**
* Denormalizes data back into an object of the given class
*
* @param mixed $data data to restore
* @param string $class the expected class to instantiate
* @param string $format format the given data was extracted from
*
* @return DateTime
*
* @throws RuntimeException If the data can't be denormalized
*/
public function denormalize($data, $class, $format = null)
{
$value = $data;
if (is_array($data)) {
if (!isset($data['@value']) || !isset($data['@type'])) {
throw new RuntimeException(
"Cannot denormalize the data as it isn't a valid JSON-LD typed value: " .
var_export($data, true)
);
}
if (self::XSD_DATETIME_IRI !== $data['@type']) {
throw new RuntimeException(
"Cannot denormalize the data as it isn't a XSD dateTime value: " .
var_export($data, true)
);
}
$value = $data['@value'];
} elseif (!is_string($data)) {
throw new RuntimeException(
"Cannot denormalize the data into a DateTime object: " .
var_export($data, true)
);
}
try {
$date = new \DateTime($value);
return $date;
} catch(Exception $e) {
throw new RuntimeException(
"Cannot denormalize the data as the value is invalid: " . var_export($data, true),
0,
$e
);
}
}
/**
* Checks whether the given class is supported for denormalization by this normalizer
*
* @param mixed $data Data to denormalize from.
* @param string $type The class to which the data should be denormalized.
* @param string $format The format being deserialized from.
*
* @return Boolean
*/
public function supportsDenormalization($data, $type, $format = null)
{
// TODO Check data?
return ('DateTime' === $type) && (self::FORMAT === $format);
}
}
@@ -53,18 +53,26 @@ class DocumentationGenerator
*/
private $reader;
/**
* @var array
*/
private $normalizers;
/**
* Constructor
*
* @param ContainerInterface $container The service container.
* @param RouterInterface $router The router.
* @param Reader $reader The annotation reader.
* @param ContainerInterface $container The service container.
* @param RouterInterface $router The router.
* @param Reader $reader The annotation reader.
* @param array $normalizers The datatype normalizers.
*/
public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader)
public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader, array $normalizers = array())
{
$this->container = $container;
$this->router = $router;
$this->reader = $reader;
$this->container = $container;
$this->router = $router;
$this->reader = $reader;
$this->normalizers = $normalizers;
}
public function isOperation(\ReflectionMethod $method)
@@ -291,6 +299,9 @@ public function getContext($type)
// TODO Make this check more robust
if (('@id' === $def['type']) || (self::HYDRA_COLLECTION === $def['type'])) {
$context[$property] = array('@id' => $context[$property], '@type' => '@id');
} elseif ($this->hasNormalizer($def['type'])) {
$normalizer = $this->getNormalizer($def['type']);
$context[$property] = array('@id' => $context[$property], '@type' => $normalizer->getTypeIri());
}
}
@@ -330,6 +341,10 @@ private function getRangeIri($vocabPrefix, $type, $arrayType)
return self::$typeMap[$type];
}
if ($this->hasNormalizer($type)) {
return $this->getElementIri($vocabPrefix, $this->getNormalizer($type)->getTypeIri());
}
$documentation = $this->getDocumentation();
if (!isset($documentation['class2type'][$type])) {
throw new \Exception('Found invalid type: ' . $type);
@@ -467,6 +482,10 @@ protected function documentType(&$documentation, $class, $group = null)
return;
}
if ($this->hasNormalizer($class)) {
return;
}
$linkRelationAnnot = 'ML\\HydraBundle\\Mapping\\LinkRelation';
$idAnnot = 'ML\\HydraBundle\\Mapping\\Id';
$exposeAnnot = 'ML\\HydraBundle\\Mapping\\Expose';
@@ -895,4 +914,28 @@ private function camelize($string)
{
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
}
/**
* Exists a normalizer for the passed class?
*
* @param string $class The class for which a normalizer is required.
*
* @return boolean Returns true if a normalizer exists, false otherwise.
*/
public function hasNormalizer($class)
{
return array_key_exists($class, $this->normalizers);
}
/**
* Gets the normalizer for the passed class
*
* @param string $class The class whose normalizer should be retrieved
*
* @return object Returns the normalizer for the specified class
*/
public function getNormalizer($class)
{
return $this->container->get($this->normalizers[$class]);
}
}
@@ -1,9 +1,32 @@
<?php
/*
* (c) Markus Lanthaler <mail@markus-lanthaler.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ML\HydraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use ML\HydraBundle\DependencyInjection\Compiler\AddDatatypeNormalizerPass;
/**
* HydraBundle
*
* @author Markus Lanthaler <mail@markus-lanthaler.com>
*/
class HydraBundle extends Bundle
{
/**
* {@inheritDoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AddDatatypeNormalizerPass());
}
}
@@ -15,6 +15,7 @@
<argument type="service" id="service_container"/>
<argument type="service" id="router" />
<argument type="service" id="annotation_reader" />
<argument type="collection" />
</service>

<service id="hydra.serializer" class="%hydra.serializer.class%">
@@ -28,6 +29,11 @@
<argument type="service" id="hydra.documentation_generator" />
<argument type="service" id="hydra.serializer" />
</service>

<!-- Core datatype normalizers -->
<service id="hydra.datatype_normalizer.datetime" class="ML\HydraBundle\DatatypeNormalizer\DateTimeNormalizer">
<tag name="hydra.datatype_normalizer" class="DateTime" />
</service>
</services>

</container>
@@ -28,13 +28,13 @@
*/
class Serializer implements SerializerInterface
{
protected $docgen;
protected $docu;
protected $types;
protected $routes;
protected $router;
public function __construct(DocumentationGenerator $documentationGenerator, RouterInterface $router)
{
$this->docgen = $documentationGenerator;
$this->docu = $documentationGenerator->getDocumentation();
$this->router = $router;
}
@@ -159,7 +159,10 @@ private function doSerialize($data, $include = false)
$value = $this->getValue($data, $definition);
if (is_array($value) || ($value instanceof \ArrayAccess) || ($value instanceof \Travesable)) {
if (is_object($value) && $this->docgen->hasNormalizer(get_class($value))) {
$normalizer = $this->docgen->getNormalizer(get_class($value));
$result[$property] = $normalizer->normalize($value);
} elseif (is_array($value) || ($value instanceof \ArrayAccess) || ($value instanceof \Travesable)) {
$result[$property] = array();
foreach ($value as $val) {
$result[$property][] = $this->doSerialize($val);

0 comments on commit 1565ba2

Please sign in to comment.