-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[WIP] [Serializer] Alias support #15200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Serializer\Annotation; | ||
|
||
use Symfony\Component\Serializer\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* Annotation class for @Alias(). | ||
* | ||
* @Annotation | ||
* @Target({"PROPERTY", "METHOD"}) | ||
* | ||
* @author Kévin Dunglas <[email protected]> | ||
*/ | ||
class Alias | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private $name; | ||
|
||
/** | ||
* @param array $data | ||
* | ||
* @throws InvalidArgumentException | ||
*/ | ||
public function __construct(array $data) | ||
{ | ||
if (!isset($data['value']) || !$data['value']) { | ||
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this))); | ||
} | ||
|
||
if (!is_string($data['value'])) { | ||
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string.', get_class($this))); | ||
} | ||
|
||
$this->name = $data['value']; | ||
} | ||
|
||
/** | ||
* Gets name. | ||
* | ||
* @return string | ||
*/ | ||
public function getName() | ||
{ | ||
return $this->name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
namespace Symfony\Component\Serializer\Mapping\Loader; | ||
|
||
use Doctrine\Common\Annotations\Reader; | ||
use Symfony\Component\Serializer\Annotation\Alias; | ||
use Symfony\Component\Serializer\Annotation\Groups; | ||
use Symfony\Component\Serializer\Exception\MappingException; | ||
use Symfony\Component\Serializer\Mapping\AttributeMetadata; | ||
|
@@ -55,13 +56,17 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) | |
} | ||
|
||
if ($property->getDeclaringClass()->name === $className) { | ||
foreach ($this->reader->getPropertyAnnotations($property) as $groups) { | ||
if ($groups instanceof Groups) { | ||
foreach ($groups->getGroups() as $group) { | ||
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) { | ||
if ($annotation instanceof Groups) { | ||
foreach ($annotation->getGroups() as $group) { | ||
$attributesMetadata[$property->name]->addGroup($group); | ||
} | ||
} | ||
|
||
if ($annotation instanceof Alias) { | ||
$attributesMetadata[$property->name]->setAlias($annotation->getName()); | ||
} | ||
|
||
$loaded = true; | ||
} | ||
} | ||
|
@@ -70,25 +75,29 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) | |
foreach ($reflectionClass->getMethods() as $method) { | ||
if ($method->getDeclaringClass()->name === $className) { | ||
foreach ($this->reader->getMethodAnnotations($method) as $groups) { | ||
if (!preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the getter name is simply the name of the property without any prefix. I think it would be good to support it too. |
||
continue; | ||
} | ||
|
||
$attributeName = lcfirst($matches[2]); | ||
|
||
if (isset($attributesMetadata[$attributeName])) { | ||
$attributeMetadata = $attributesMetadata[$attributeName]; | ||
} else { | ||
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); | ||
$classMetadata->addAttributeMetadata($attributeMetadata); | ||
} | ||
|
||
if ($groups instanceof Groups) { | ||
if (preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches)) { | ||
$attributeName = lcfirst($matches[2]); | ||
|
||
if (isset($attributesMetadata[$attributeName])) { | ||
$attributeMetadata = $attributesMetadata[$attributeName]; | ||
} else { | ||
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); | ||
$classMetadata->addAttributeMetadata($attributeMetadata); | ||
} | ||
|
||
foreach ($groups->getGroups() as $group) { | ||
$attributeMetadata->addGroup($group); | ||
} | ||
} else { | ||
throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); | ||
foreach ($groups->getGroups() as $group) { | ||
$attributeMetadata->addGroup($group); | ||
} | ||
} | ||
|
||
if ($annotation instanceof Alias) { | ||
$attributeMetadata[$property->name]->setAlias($annotation->getName()); | ||
} | ||
|
||
$loaded = true; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Serializer\NameConverter; | ||
|
||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; | ||
|
||
/** | ||
* Converts name of properties using the alias specified in the attribute metadata. | ||
* | ||
* @author Kévin Dunglas <[email protected]> | ||
*/ | ||
class AliasNameConverter implements NameConverterInterface | ||
{ | ||
/** | ||
* @var ClassMetadataFactoryInterface | ||
*/ | ||
private $classMetadataFactory; | ||
|
||
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory) | ||
{ | ||
$this->classMetadataFactory = $classMetadataFactory; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function normalize($object, $propertyName) | ||
{ | ||
$attributesMetadata = $this->getAttributesMetadata($object); | ||
|
||
if (isset($attributesMetadata[$propertyName]) && $alias = $attributesMetadata[$propertyName]->getAlias()) { | ||
return $alias; | ||
} | ||
|
||
return $propertyName; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function denormalize($object, $propertyName) | ||
{ | ||
$attributesMetadata = $this->getAttributesMetadata($object); | ||
|
||
// TODO: cache that | ||
foreach ($attributesMetadata as $attributeMetadata) { | ||
$alias = $attributeMetadata->getAlias(); | ||
if ($propertyName === $alias) { | ||
return $alias; | ||
} | ||
} | ||
|
||
return $propertyName; | ||
} | ||
|
||
/** | ||
* Gets attributes metadata. | ||
* | ||
* @param string|object $class | ||
* | ||
* @return array | ||
*/ | ||
private function getAttributesMetadata($class) | ||
{ | ||
if (is_object($class)) { | ||
$class = get_class($class); | ||
} | ||
|
||
return $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
namespace Symfony\Component\Serializer\NameConverter; | ||
|
||
/** | ||
* CamelCase to Underscore name converter. | ||
* Converts names of properties from CamelCase to Underscore. | ||
* | ||
* @author Kévin Dunglas <[email protected]> | ||
*/ | ||
|
@@ -40,7 +40,7 @@ public function __construct(array $attributes = null, $lowerCamelCase = true) | |
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function normalize($propertyName) | ||
public function normalize($object, $propertyName) | ||
{ | ||
if (null === $this->attributes || in_array($propertyName, $this->attributes)) { | ||
$snakeCasedName = ''; | ||
|
@@ -63,7 +63,7 @@ public function normalize($propertyName) | |
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function denormalize($propertyName) | ||
public function denormalize($object, $propertyName) | ||
{ | ||
$camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { | ||
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,16 +21,20 @@ interface NameConverterInterface | |
/** | ||
* Converts a property name to its normalized value. | ||
* | ||
* @param string $propertyName | ||
* @param string|object $class | ||
* @param string $propertyName | ||
* | ||
* @return string | ||
*/ | ||
public function normalize($propertyName); | ||
public function normalize($class, $propertyName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An easy way to limit the BC break is to inverse parameters (same for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the BC promise, you'd need to make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @weaverryan even if you make it optional, it is still a BC break: people implementing the interface will have to change their code, otherwise they will get PHP errors about signature mismatches. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm reading this from (my recent new best friend) out BC promise: http://symfony.com/doc/current/contributing/code/bc.html#changing-interfaces for non-API interfaces, we are allowed to add optional arguments to methods. Am I reading that wrong? Or perhaps that's not correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it says And actually, I don't think any code added since 2.3 has actually been tagged as |
||
|
||
/** | ||
* Converts a property name to its denormalized value. | ||
* | ||
* @param string $propertyName | ||
* @param string|object $class | ||
* @param string $propertyName | ||
* | ||
* @return string | ||
*/ | ||
public function denormalize($propertyName); | ||
public function denormalize($class, $propertyName); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the SerializedName from JMS more than alias, which seems to tell me less about what it does. Any reason for using alias specifically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weaverryan agree with you, but
Alias
is still understandable