Created
April 30, 2011 09:48
-
-
Save immutef/949563 to your computer and use it in GitHub Desktop.
Normalizers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Turtle\ApiBundle\Serializer\Normalizer; | |
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer, | |
Symfony\Component\Serializer\SerializerInterface; | |
class DateTimeNormalizer extends AbstractNormalizer | |
{ | |
/** | |
* @var string | |
*/ | |
protected $format; | |
/** | |
* Constructor. | |
* | |
* @param string $format (optional) | |
* @return void | |
*/ | |
public function __construct($format = \DateTime::RFC3339) | |
{ | |
$this->format = $format; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function normalize($object, $format, $properties = null) | |
{ | |
return $object->format($this->format); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function denormalize($data, $class, $format = null) | |
{ | |
return $class::createFromFormat($this->format, $data); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function supports(\ReflectionClass $class, $format = null) | |
{ | |
if ('DateTime' == $class->getName()) { | |
return true; | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Turtle\ApiBundle\Serializer\Normalizer; | |
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer, | |
Symfony\Component\Serializer\SerializerInterface; | |
class DoctrineCollectionNormalizer extends AbstractNormalizer | |
{ | |
/** | |
* {@inheritDoc} | |
*/ | |
public function normalize($object, $format, $properties = null) | |
{ | |
$attributes = array(); | |
foreach ($object as $attributeName => $attributeValue) { | |
$attributes[$attributeName] = $this->serializer->normalizeObject($attributeValue, $format, $properties); | |
} | |
return $attributes; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function denormalize($data, $class, $format = null) | |
{ | |
throw new \BadMethodCallException('Denormalization of a Doctrine Collection is not possible.'); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function supports(\ReflectionClass $class, $format = null) | |
{ | |
if ($class->implementsInterface('Doctrine\\Common\\Collections\\Collection')) { | |
return true; | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Turtle\ApiBundle\Entity; | |
use Doctrine\Common\NotifyPropertyChanged, | |
Doctrine\Common\PropertyChangedListener; | |
class Entity implements NotifyPropertyChanged, \ArrayAccess | |
{ | |
/** | |
* @var array | |
*/ | |
private $_listeners = array(); | |
/** | |
* {@inheritDoc} | |
*/ | |
public function addPropertyChangedListener(PropertyChangedListener $listener) { | |
$this->_listeners[] = $listener; | |
} | |
/** | |
* @param string $propName | |
* @param mixed $oldValue | |
* @param mixed $newValue | |
*/ | |
protected function _onPropertyChanged($propName, $oldValue, $newValue) { | |
if (count($this->_listeners) > 0) { | |
foreach ($this->_listeners as $listener) { | |
$listener->propertyChanged($this, $propName, $oldValue, $newValue); | |
} | |
} | |
} | |
/** | |
* Retrieve value of a property. | |
* | |
* @param string $field | |
* @return mixed | |
*/ | |
public function get($field) | |
{ | |
return $this->offsetGet($field); | |
} | |
/** | |
* Set value of a property. | |
* | |
* @param string $field | |
* @param mixed $value | |
* @return void | |
*/ | |
public function set($field, $value) | |
{ | |
$this->offsetSet($field, $value); | |
} | |
/** | |
* Exists if a property is set. | |
* | |
* @param string $field | |
* @return boolean | |
*/ | |
public function has($field) | |
{ | |
return $this->offsetExists($field); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function __call($method, array $arguments = array()) | |
{ | |
$match = null; | |
if (!preg_match('/^(get|set|has)_([a-zA-Z0-9_]+)$/', preg_replace('/([A-Z])/', '_$1', $method), $match)) { | |
throw new \BadMethodCallException(sprintf('Method "%s" does not exist in "%s" entity.', $method, get_class($this))); | |
} | |
list(, $type, $field) = $match; | |
$field = strtolower($field); | |
if ('get' == $type) { | |
return $this->offsetGet($field); | |
} | |
if ('has' == $type) { | |
return $this->offsetExists($field); | |
} | |
if (!count($arguments)) { | |
$arguments = array(null); | |
} | |
$this->offsetSet($field, current($arguments)); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function __get($field) | |
{ | |
return $this->offsetGet($field); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function __set($field, $value) | |
{ | |
$this->offsetSet($field, $value); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function __isset($field) | |
{ | |
return $this->offsetExists($field); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function __unset($field) | |
{ | |
return $this->offsetUnset($field); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function offsetGet($field) | |
{ | |
$method = 'get'.$this->normalizeMethod($field); | |
if ($this->isMethodAccessible($method)) { | |
return $this->$method(); | |
} | |
if ($this->isPropertyAccessible($field)) { | |
return $this->$field; | |
} | |
if ($this->isIdField($field)) { | |
return $this->offsetGet($this->normalizeIdField($field)); | |
} | |
throw new \BadMethodCallException(sprintf('Field "%s" does not exist in "%s" entity.', $field, get_class($this))); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function offsetSet($field, $value) | |
{ | |
$method = 'set'.$this->normalizeMethod($field); | |
if ($this->isMethodAccessible($method)) { | |
return $this->$method($value); | |
} | |
if ($this->isIdField($field)) { | |
return $this->offsetSet($this->normalizeIdField($field), $value); | |
} | |
if (!$this->isPropertyAccessible($field)) { | |
throw new \BadMethodCallException(sprintf('Field "%s" does not exist in "%s" entity.', $field, get_class($this))); | |
} | |
if ($value != $this->$field) { | |
$this->_onPropertyChanged($field, $this->$field, $value); | |
$this->$field = $value; | |
} | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function offsetExists($field) | |
{ | |
$method = 'has'.$this->normalizeMethod($field); | |
if ($this->isMethodAccessible($method)) { | |
return $this->$method(); | |
} | |
$method = 'get'.$this->normalizeMethod($field); | |
if (method_exists($this, $method)) { | |
return null !== $this->$method(); | |
} | |
if ($this->isPropertyAccessible($field)) { | |
return null !== $this->$field; | |
} | |
if ($this->isIdField($field)) { | |
return $this->offsetExists($this->normalizeIdField($field)); | |
} | |
throw new \BadMethodCallException(sprintf('Field "%s" does not exist in "%s" entity.', $field, get_class($this))); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function offsetUnset($field) | |
{ | |
$this->offsetSet($field, null); | |
} | |
/** | |
* @param string $field | |
* @return string | |
*/ | |
private function normalizeField($field) | |
{ | |
return strtr($field, array('_' => '', '.' => '_')); | |
} | |
/** | |
* @param string $field | |
* @return string | |
*/ | |
private function normalizeIdField($field) | |
{ | |
return rtrim(substr($this->normalizeField($field), 0, -2), '_'); | |
} | |
/** | |
* @param string $field | |
* @return string | |
*/ | |
private function normalizeMethod($field) | |
{ | |
return ucfirst($this->normalizeField($field)); | |
} | |
/** | |
* @param string $field | |
* @return boolean | |
*/ | |
private function isIdField($field) | |
{ | |
return 'id' == substr(strtolower($field), -2); | |
} | |
/** | |
* @param string $method | |
* @return boolean | |
*/ | |
private function isMethodAccessible($method) | |
{ | |
if (!method_exists($this, $method)) { | |
return false; | |
} | |
$reflMethod = new \ReflectionMethod($this, $method); | |
if (!$reflMethod->isPublic()) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* @param string $property | |
* @return boolean | |
*/ | |
private function isPropertyAccessible($property) | |
{ | |
if ('_' == substr($property, 0, 1) || !property_exists($this, $property)) { | |
return false; | |
} | |
$reflProp = new \ReflectionProperty($this, $property); | |
if ($reflProp->isPrivate() || $reflProp->isStatic()) { | |
return false; | |
} | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Turtle\ApiBundle\Serializer\Normalizer; | |
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer, | |
Symfony\Component\Serializer\SerializerInterface, | |
Symfony\Bundle\DoctrineBundle\Registry; | |
class EntityNormalizer extends AbstractNormalizer | |
{ | |
/** | |
* @var Symfony\Bundle\DoctrineBundle\Registry $registry | |
*/ | |
private $registry; | |
/** | |
* Constructor. | |
* | |
* @param Symfony\Bundle\DoctrineBundle\Registry $registry | |
* @return void | |
*/ | |
public function __construct(Registry $registry) | |
{ | |
$this->registry = $registry; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function normalize($object, $format, $properties = null) | |
{ | |
$propertyMap = (null === $properties) ? null : array_map('strtolower', $properties); | |
$metadata = $this->getMetadata(get_class($object)); | |
$properties = $metadata->getReflectionProperties(); | |
$attributes = array(); | |
foreach ($properties as $property) { | |
$attributeName = $property->getName(); | |
if ($this->isPropertyAccessible($property) && (null === $propertyMap || in_array($attributeName, $propertyMap))) { | |
$attributeValue = $object->get($attributeName); | |
if ($this->serializer->isStructuredType($attributeValue)) { | |
if ($metadata->isSingleValuedAssociation($attributeName)) { | |
$associcationProperties = $this->getMetadata(get_class($attributeValue))->getIdentifierFieldNames(); | |
$attributeValue = $this->serializer->normalizeObject($attributeValue, $format, $associcationProperties); | |
} else if ($metadata->isCollectionValuedAssociation($attributeName)) { | |
$associationMapping = $metadata->getAssociationMapping($attributeName); | |
$associcationProperties = $this->getMetadata($associationMapping['targetEntity'])->getIdentifierFieldNames(); | |
$attributeValue = $this->serializer->normalizeObject($attributeValue, $format, $associcationProperties); | |
} else { | |
$attributeValue = $this->serializer->normalize($attributeValue, $format); | |
} | |
} | |
$attributes[$attributeName] = $attributeValue; | |
} | |
} | |
return $attributes; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function denormalize($data, $class, $format = null) | |
{ | |
$metadata = $this->getMetadata($class); | |
$reflectionClass = $metadata->getReflectionClass(); | |
$constructor = $reflectionClass->getConstructor(); | |
if ($constructor) { | |
$constructorParameters = $constructor->getParameters(); | |
$parameters = array(); | |
foreach ($constructorParameters as $constructorParameter) { | |
$parameterName = strtolower($constructorParameter->getName()); | |
if (isset($data[$parameterName])) { | |
$parameters[] = $data[$parameterName]; | |
unset($data[$parameterName]); | |
} else if (!$constructorParameter->isOptional()) { | |
throw new \RuntimeException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $parameterName)); | |
} | |
} | |
$object = $reflectionClass->newInstanceArgs($parameters); | |
} else { | |
$object = new $class; | |
} | |
foreach ($data as $attributeName => $attributeValue) { | |
$object->set($attributeName, $attributeValue); | |
} | |
return $object; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function supports(\ReflectionClass $class, $format = null) | |
{ | |
if ($class->isSubclassOf('Turtle\\ApiBundle\\Entity\\Entity')) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @return Doctrine\ORM\Mapping\ClassMetadata | |
*/ | |
private function getMetadata($class) | |
{ | |
return $this->registry | |
->getEntityManager() | |
->getMetadataFactory() | |
->getMetadataFor($class); | |
} | |
/** | |
* @param \ReflectionProperty $reflProp | |
* @return boolean | |
*/ | |
private function isPropertyAccessible(\ReflectionProperty $reflProp) | |
{ | |
if ($reflProp->isPrivate() || $reflProp->isStatic() || '_' == substr($reflProp->getName(), 0, 1)) { | |
return false; | |
} | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<response> | |
<match> | |
<id>550</id> | |
<!-- participants: Collection Valued Association --> | |
<participants> | |
<id>21</id> | |
</participants> | |
<participants> | |
<id>22</id> | |
</participants> | |
<is_played>0</is_played> | |
<created_at>2011-05-04T00:08:43+02:00</created_at> | |
<updated_at>2011-05-04T00:08:43+02:00</updated_at> | |
</match> | |
</response> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<response> | |
<participant> | |
<id>21</id> | |
<match><!-- match: Single Valued Association --> | |
<id>550</id> | |
</match> | |
<created_at>2011-05-04T00:12:30+02:00</created_at> | |
<updated_at/> | |
</participant> | |
<participant> | |
<id>22</id> | |
<match><!-- match: Single Valued Association --> | |
<id>550</id> | |
</match> | |
<created_at>2011-05-04T00:12:30+02:00</created_at> | |
<updated_at/> | |
</participant> | |
</response> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Versus\RankedMatchBundle\Entity; | |
use Turtle\ApiBundle\Entity\Entity; | |
use Doctrine\Common\Collections\ArrayCollection; | |
/** | |
* @orm:Entity(repositoryClass="Versus\RankedMatchBundle\Entity\MatchRepository") | |
* @orm:Table(name="matches") | |
* @orm:HasLifecycleCallbacks | |
*/ | |
class Match extends Entity | |
{ | |
/** | |
* @var integer | |
* @orm:Id | |
* @orm:Column(type="bigint") | |
* @orm:GeneratedValue(strategy="AUTO") | |
*/ | |
protected $id; | |
/** | |
* @var \Doctrine\Common\Collections\ArrayCollection | |
* @orm:OneToMany(targetEntity="Participant", mappedBy="match") | |
*/ | |
protected $participants; | |
/** | |
* @var boolean | |
* @orm:Column(type="boolean") | |
*/ | |
protected $is_played; | |
/** | |
* @var \DateTime | |
* @gedmo:Timestampable(on="create") | |
* @orm:Column(type="datetime") | |
*/ | |
protected $created_at; | |
/** | |
* @var \DateTime | |
* @gedmo:Timestampable(on="update") | |
* @orm:Column(type="datetime", nullable=true) | |
*/ | |
protected $updated_at; | |
/** | |
* Constructor. | |
* | |
* @return void | |
*/ | |
public function __construct() | |
{ | |
$this->is_played = false; | |
$this->created_at = new \DateTime('now'); | |
$this->participants = new ArrayCollection(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Versus\RankedMatchBundle\Entity; | |
use Turtle\ApiBundle\Entity\Entity; | |
/** | |
* @orm:Entity(repositoryClass="Versus\RankedMatchBundle\Entity\ParticipantRepository") | |
* @orm:Table(name="participants") | |
* @orm:HasLifecycleCallbacks | |
*/ | |
class Participant extends Entity | |
{ | |
/** | |
* @var integer | |
* @orm:Id | |
* @orm:Column(type="bigint") | |
* @orm:GeneratedValue(strategy="AUTO") | |
*/ | |
protected $id; | |
/** | |
* @var Versus\RankedMatchBundle\Entity\Match | |
* @orm:ManyToOne(targetEntity="Match", inversedBy="participants") | |
* @orm:JoinColumn(referencedColumnName="id", nullable=false) | |
*/ | |
protected $match; | |
/** | |
* @var \DateTime | |
* @gedmo:Timestampable(on="create") | |
* @orm:Column(type="datetime") | |
*/ | |
protected $created_at; | |
/** | |
* @var \DateTime | |
* @gedmo:Timestampable(on="update") | |
* @orm:Column(type="datetime", nullable=true) | |
*/ | |
protected $updated_at; | |
/** | |
* Constructor. | |
* | |
* @return void | |
*/ | |
public function __construct() | |
{ | |
$this->created_at = new \DateTime('now'); | |
} | |
} |
No, but it generates generic getter and setter methods which could be used by this Normalizer. My "magic" Entity just used PHP's magic methods to access properties via methods/array access, thus the GetSetNormalizer simply did not work properly. With Fabien's SensioGeneratorBundle it's just a lot easier to generate Entities without Propel2 and use 'em with RestBundle/SerializerBundle.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh so with propel all your entities return data structures simple enough for even GetSetMethodNormalizer to understand? Isn't the Symfony 2 support sub-par in comparison to Doctrine support? Thanks